summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Install/ThirdPartyLicenses/LibEvent-LICENSE.txt99
-rw-r--r--MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.cmd1
-rw-r--r--Tools/RCONClient/Globals.h42
-rw-r--r--Tools/RCONClient/RCONClient.cpp19
-rw-r--r--Tools/RCONClient/RCONClient.sln8
-rw-r--r--src/ClientHandle.cpp245
-rw-r--r--src/ClientHandle.h84
-rw-r--r--src/Entities/Player.cpp24
-rw-r--r--src/Entities/Player.h18
-rw-r--r--src/Generating/BioGen.cpp3
-rw-r--r--src/HTTPServer/HTTPConnection.cpp86
-rw-r--r--src/HTTPServer/HTTPConnection.h39
-rw-r--r--src/HTTPServer/HTTPMessage.cpp5
-rw-r--r--src/HTTPServer/HTTPServer.cpp134
-rw-r--r--src/HTTPServer/HTTPServer.h52
-rw-r--r--src/HTTPServer/SslHTTPConnection.cpp33
-rw-r--r--src/HTTPServer/SslHTTPConnection.h4
-rw-r--r--src/IniFile.cpp36
-rw-r--r--src/IniFile.h24
-rw-r--r--src/OSSupport/CMakeLists.txt6
-rw-r--r--src/OSSupport/File.h2
-rw-r--r--src/OSSupport/ListenThread.cpp238
-rw-r--r--src/OSSupport/ListenThread.h85
-rw-r--r--src/OSSupport/NetworkSingleton.cpp44
-rw-r--r--src/OSSupport/NetworkSingleton.h11
-rw-r--r--src/OSSupport/ServerHandleImpl.cpp10
-rw-r--r--src/OSSupport/SocketThreads.cpp702
-rw-r--r--src/OSSupport/SocketThreads.h194
-rw-r--r--src/OSSupport/TCPLinkImpl.cpp10
-rw-r--r--src/PolarSSL++/BlockingSslClientSocket.cpp176
-rw-r--r--src/PolarSSL++/BlockingSslClientSocket.h35
-rw-r--r--src/PolarSSL++/SslContext.cpp2
-rw-r--r--src/Protocol/Protocol18x.cpp25
-rw-r--r--src/RCONServer.cpp198
-rw-r--r--src/RCONServer.h69
-rw-r--r--src/Root.cpp64
-rw-r--r--src/Server.cpp271
-rw-r--r--src/Server.h97
-rw-r--r--src/StringUtils.cpp44
-rw-r--r--src/StringUtils.h11
-rw-r--r--src/WebAdmin.cpp75
-rw-r--r--src/WebAdmin.h17
-rw-r--r--src/World.cpp31
-rw-r--r--src/World.h9
-rw-r--r--src/main.cpp11
-rw-r--r--tests/Network/EchoServer.cpp14
-rw-r--r--tests/Network/Google.cpp15
-rw-r--r--tests/Network/NameLookup.cpp12
48 files changed, 1363 insertions, 2071 deletions
diff --git a/Install/ThirdPartyLicenses/LibEvent-LICENSE.txt b/Install/ThirdPartyLicenses/LibEvent-LICENSE.txt
new file mode 100644
index 000000000..402ca5089
--- /dev/null
+++ b/Install/ThirdPartyLicenses/LibEvent-LICENSE.txt
@@ -0,0 +1,99 @@
+Libevent is available for use under the following license, commonly known
+as the 3-clause (or "modified") BSD license:
+
+==============================
+Copyright (c) 2000-2007 Niels Provos <provos@citi.umich.edu>
+Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+==============================
+
+Portions of Libevent are based on works by others, also made available by
+them under the three-clause BSD license above. The copyright notices are
+available in the corresponding source files; the license is as above. Here's
+a list:
+
+log.c:
+ Copyright (c) 2000 Dug Song <dugsong@monkey.org>
+ Copyright (c) 1993 The Regents of the University of California.
+
+strlcpy.c:
+ Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+
+win32select.c:
+ Copyright (c) 2003 Michael A. Davis <mike@datanerds.net>
+
+evport.c:
+ Copyright (c) 2007 Sun Microsystems
+
+ht-internal.h:
+ Copyright (c) 2002 Christopher Clark
+
+minheap-internal.h:
+ Copyright (c) 2006 Maxim Yegorushkin <maxim.yegorushkin@gmail.com>
+
+==============================
+
+The arc4module is available under the following, sometimes called the
+"OpenBSD" license:
+
+ Copyright (c) 1996, David Mazieres <dm@uun.org>
+ Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+
+ Permission to use, copy, modify, and distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+==============================
+
+The Windows timer code is based on code from libutp, which is
+distributed under this license, sometimes called the "MIT" license.
+
+
+Copyright (c) 2010 BitTorrent, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.cmd b/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.cmd
index 3ea6963b4..7022ef12a 100644
--- a/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.cmd
+++ b/MCServer/webadmin/GenerateSelfSignedHTTPSCertUsingOpenssl.cmd
@@ -1,3 +1,4 @@
+@echo off
echo This script generates the certificate and private key for the https webadmin
echo Note that the generated certificate is self-signed, and therefore not trusted by browsers
echo Note that this script requires openssl to be installed and in PATH
diff --git a/Tools/RCONClient/Globals.h b/Tools/RCONClient/Globals.h
index a3a2f2846..dc7669270 100644
--- a/Tools/RCONClient/Globals.h
+++ b/Tools/RCONClient/Globals.h
@@ -22,6 +22,18 @@
#define ALIGN_8
#define ALIGN_16
+ #define FORMATSTRING(formatIndex, va_argsIndex)
+
+ // MSVC has its own custom version of zu format
+ #define SIZE_T_FMT "%Iu"
+ #define SIZE_T_FMT_PRECISION(x) "%" #x "Iu"
+ #define SIZE_T_FMT_HEX "%Ix"
+
+ #define NORETURN __declspec(noreturn)
+
+ // Use non-standard defines in <cmath>
+ #define _USE_MATH_DEFINES
+
#elif defined(__GNUC__)
// TODO: Can GCC explicitly mark classes as abstract (no instances can be created)?
@@ -38,6 +50,29 @@
// Some portability macros :)
#define stricmp strcasecmp
+ #define FORMATSTRING(formatIndex, va_argsIndex) __attribute__((format (printf, formatIndex, va_argsIndex)))
+
+ #if defined(_WIN32)
+ // We're compiling on MinGW, which uses an old MSVCRT library that has no support for size_t printfing.
+ // We need direct size formats:
+ #if defined(_WIN64)
+ #define SIZE_T_FMT "%I64u"
+ #define SIZE_T_FMT_PRECISION(x) "%" #x "I64u"
+ #define SIZE_T_FMT_HEX "%I64x"
+ #else
+ #define SIZE_T_FMT "%u"
+ #define SIZE_T_FMT_PRECISION(x) "%" #x "u"
+ #define SIZE_T_FMT_HEX "%x"
+ #endif
+ #else
+ // We're compiling on Linux, so we can use libc's size_t printf format:
+ #define SIZE_T_FMT "%zu"
+ #define SIZE_T_FMT_PRECISION(x) "%" #x "zu"
+ #define SIZE_T_FMT_HEX "%zx"
+ #endif
+
+ #define NORETURN __attribute((__noreturn__))
+
#else
#error "You are using an unsupported compiler, you might need to #define some stuff here for your compiler"
@@ -74,6 +109,8 @@ typedef unsigned long long UInt64;
typedef unsigned int UInt32;
typedef unsigned short UInt16;
+typedef unsigned char Byte;
+
@@ -94,7 +131,7 @@ typedef unsigned short UInt16;
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
- #define _WIN32_WINNT 0x501 // We want to target WinXP and higher
+ #define _WIN32_WINNT 0x502 // We want to target WinXP SP2 and higher
#include <Windows.h>
#include <winsock2.h>
@@ -175,7 +212,8 @@ typedef unsigned short UInt16;
#include "StringUtils.h"
#include "OSSupport/CriticalSection.h"
#include "OSSupport/File.h"
-#include "MCLogger.h"
+#include "OSSupport/Event.h"
+#include "Logger.h"
diff --git a/Tools/RCONClient/RCONClient.cpp b/Tools/RCONClient/RCONClient.cpp
index 288363a66..7d6cf6d8f 100644
--- a/Tools/RCONClient/RCONClient.cpp
+++ b/Tools/RCONClient/RCONClient.cpp
@@ -80,14 +80,14 @@ bool cRCONPacketizer::SendPacket(int a_PacketType, const AString & a_PacketPaylo
size_t Length = Packet.size();
if (!m_Socket.Send((const char *)&Length, 4))
{
- fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.",
+ fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.\n",
cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
);
return false;
}
if (!m_Socket.Send(Packet.data(), Packet.size()))
{
- fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.",
+ fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.\n",
cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
);
return false;
@@ -110,12 +110,12 @@ bool cRCONPacketizer::ReceiveResponse(void)
int NumReceived = m_Socket.Receive(buf, sizeof(buf), 0);
if (NumReceived == 0)
{
- fprintf(stderr, "The remote end closed the connection. Aborting.");
+ fprintf(stderr, "The remote end closed the connection. Aborting.\n");
return false;
}
if (NumReceived < 0)
{
- fprintf(stderr, "Network error while receiving response: %d, %d (%s). Aborting.",
+ fprintf(stderr, "Network error while receiving response: %d, %d (%s). Aborting.\n",
NumReceived, cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
);
return false;
@@ -156,13 +156,13 @@ bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength)
{
if ((RequestID == -1) && (m_RequestID == 0))
{
- fprintf(stderr, "Login failed. Aborting.");
+ fprintf(stderr, "Login failed. Aborting.\n");
IsValid = false;
// Continue, so that the payload is printed before the program aborts.
}
else
{
- fprintf(stderr, "The server returned an invalid request ID, got %d, exp. %d. Aborting.", RequestID, m_RequestID);
+ fprintf(stderr, "The server returned an invalid request ID, got %d, exp. %d. Aborting.\n", RequestID, m_RequestID);
return false;
}
}
@@ -172,7 +172,7 @@ bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength)
VERIFY(a_Buffer.ReadLEInt(PacketType));
if (PacketType != ptCommand)
{
- fprintf(stderr, "The server returned an unknown packet type: %d. Aborting.", PacketType);
+ fprintf(stderr, "The server returned an unknown packet type: %d. Aborting.\n", PacketType);
IsValid = false;
// Continue, so that the payload is printed before the program aborts.
}
@@ -200,8 +200,8 @@ bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength)
int RealMain(int argc, char * argv[])
{
- new cMCLogger; // Create a new logger
-
+ cLogger::InitiateMultithreading();
+
// Parse the cmdline params for server IP, port, password and the commands to send:
AString ServerAddress, Password;
int ServerPort = -1;
@@ -301,6 +301,7 @@ int RealMain(int argc, char * argv[])
}
}
+ // Send each command:
for (AStringVector::const_iterator itr = Commands.begin(), end = Commands.end(); itr != end; ++itr)
{
if (g_IsVerbose)
diff --git a/Tools/RCONClient/RCONClient.sln b/Tools/RCONClient/RCONClient.sln
index 0a8596e43..5c977fc81 100644
--- a/Tools/RCONClient/RCONClient.sln
+++ b/Tools/RCONClient/RCONClient.sln
@@ -1,7 +1,9 @@

-Microsoft Visual Studio Solution File, Format Version 10.00
-# Visual C++ Express 2008
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RCONClient", "RCONClient.vcproj", "{1A48B032-07D0-4DDD-8362-66C0FC7F7849}"
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Express 2013 for Windows Desktop
+VisualStudioVersion = 12.0.31101.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RCONClient", "RCONClient.vcxproj", "{1A48B032-07D0-4DDD-8362-66C0FC7F7849}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp
index 387cc4628..c774a92c2 100644
--- a/src/ClientHandle.cpp
+++ b/src/ClientHandle.cpp
@@ -15,7 +15,6 @@
#include "Item.h"
#include "Mobs/Monster.h"
#include "ChatColor.h"
-#include "OSSupport/Socket.h"
#include "Items/ItemHandler.h"
#include "Blocks/BlockHandler.h"
#include "Blocks/BlockSlab.h"
@@ -56,16 +55,15 @@ int cClientHandle::s_ClientCount = 0;
////////////////////////////////////////////////////////////////////////////////
// cClientHandle:
-cClientHandle::cClientHandle(const cSocket * a_Socket, int a_ViewDistance) :
+cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) :
m_CurrentViewDistance(a_ViewDistance),
m_RequestedViewDistance(a_ViewDistance),
- m_IPString(a_Socket->GetIPString()),
- m_OutgoingData(64 KiB),
+ m_IPString(a_IPString),
m_Player(nullptr),
m_HasSentDC(false),
m_LastStreamedChunkX(0x7fffffff), // bogus chunk coords to force streaming upon login
m_LastStreamedChunkZ(0x7fffffff),
- m_TimeSinceLastPacket(0),
+ m_TicksSinceLastPacket(0),
m_Ping(1000),
m_PingID(1),
m_BlockDigAnimStage(-1),
@@ -135,9 +133,6 @@ cClientHandle::~cClientHandle()
SendDisconnect("Server shut down? Kthnxbai");
}
- // Close the socket as soon as it sends all outgoing data:
- cRoot::Get()->GetServer()->RemoveClient(this);
-
delete m_Protocol;
m_Protocol = nullptr;
@@ -151,6 +146,10 @@ cClientHandle::~cClientHandle()
void cClientHandle::Destroy(void)
{
{
+ cCSLock Lock(m_CSOutgoingData);
+ m_Link.reset();
+ }
+ {
cCSLock Lock(m_CSDestroyingState);
if (m_State >= csDestroying)
{
@@ -168,6 +167,10 @@ void cClientHandle::Destroy(void)
RemoveFromAllChunks();
m_Player->GetWorld()->RemoveClientFromChunkSender(this);
}
+ if (m_Player != nullptr)
+ {
+ m_Player->RemoveClientHandle();
+ }
m_State = csDestroyed;
}
@@ -326,7 +329,8 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID,
m_Protocol->SendLoginSuccess();
// Spawn player (only serversided, so data is loaded)
- m_Player = new cPlayer(this, GetUsername());
+ m_Player = new cPlayer(m_Self, GetUsername());
+ m_Self.reset();
cWorld * World = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName());
if (World == nullptr)
@@ -689,6 +693,47 @@ void cClientHandle::HandleCreativeInventory(short a_SlotNum, const cItem & a_Hel
+void cClientHandle::HandleEnchantItem(Byte a_WindowID, Byte a_Enchantment)
+{
+ if (a_Enchantment > 2)
+ {
+ LOGWARNING("%s attempt to crash the server with invalid enchanting selection!", GetUsername().c_str());
+ Kick("Invalid enchanting!");
+ return;
+ }
+
+ if (
+ (m_Player->GetWindow() == nullptr) ||
+ (m_Player->GetWindow()->GetWindowID() != a_WindowID) ||
+ (m_Player->GetWindow()->GetWindowType() != cWindow::wtEnchantment)
+ )
+ {
+ return;
+ }
+
+ cEnchantingWindow * Window = reinterpret_cast<cEnchantingWindow *>(m_Player->GetWindow());
+ cItem Item = *Window->m_SlotArea->GetSlot(0, *m_Player); // Make a copy of the item
+ short BaseEnchantmentLevel = Window->GetPropertyValue(a_Enchantment);
+
+ if (Item.EnchantByXPLevels(BaseEnchantmentLevel))
+ {
+ if (m_Player->IsGameModeCreative() || m_Player->DeltaExperience(-m_Player->XpForLevel(BaseEnchantmentLevel)) >= 0)
+ {
+ Window->m_SlotArea->SetSlot(0, *m_Player, Item);
+ Window->SendSlot(*m_Player, Window->m_SlotArea, 0);
+ Window->BroadcastWholeWindow();
+
+ Window->SetProperty(0, 0, *m_Player);
+ Window->SetProperty(1, 0, *m_Player);
+ Window->SetProperty(2, 0, *m_Player);
+ }
+ }
+}
+
+
+
+
+
void cClientHandle::HandlePlayerAbilities(bool a_CanFly, bool a_IsFlying, float FlyingSpeed, float WalkingSpeed)
{
UNUSED(FlyingSpeed); // Ignore the client values for these
@@ -1777,44 +1822,9 @@ void cClientHandle::SendData(const char * a_Data, size_t a_Size)
// This could crash the client, because they've already unloaded the world etc., and suddenly a wild packet appears (#31)
return;
}
-
- {
- cCSLock Lock(m_CSOutgoingData);
-
- // _X 2012_09_06: We need an overflow buffer, usually when streaming the initial chunks
- if (m_OutgoingDataOverflow.empty())
- {
- // No queued overflow data; if this packet fits into the ringbuffer, put it in, otherwise put it in the overflow buffer:
- size_t CanFit = m_OutgoingData.GetFreeSpace();
- if (CanFit > a_Size)
- {
- CanFit = a_Size;
- }
- if (CanFit > 0)
- {
- m_OutgoingData.Write(a_Data, CanFit);
- }
- if (a_Size > CanFit)
- {
- m_OutgoingDataOverflow.append(a_Data + CanFit, a_Size - CanFit);
- }
- }
- else
- {
- // There is a queued overflow. Append to it, then send as much from its front as possible
- m_OutgoingDataOverflow.append(a_Data, a_Size);
- size_t CanFit = m_OutgoingData.GetFreeSpace();
- if (CanFit > 128)
- {
- // No point in moving the data over if it's not large enough - too much effort for too little an effect
- m_OutgoingData.Write(m_OutgoingDataOverflow.data(), CanFit);
- m_OutgoingDataOverflow.erase(0, CanFit);
- }
- }
- } // Lock(m_CSOutgoingData)
-
- // Notify SocketThreads that we have something to write:
- cRoot::Get()->GetServer()->NotifyClientWrite(this);
+
+ cCSLock Lock(m_CSOutgoingData);
+ m_OutgoingData.append(a_Data, a_Size);
}
@@ -1871,10 +1881,24 @@ void cClientHandle::Tick(float a_Dt)
cCSLock Lock(m_CSIncomingData);
std::swap(IncomingData, m_IncomingData);
}
- m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
+ if (!IncomingData.empty())
+ {
+ m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
+ }
+
+ // Send any queued outgoing data:
+ AString OutgoingData;
+ {
+ cCSLock Lock(m_CSOutgoingData);
+ std::swap(OutgoingData, m_OutgoingData);
+ }
+ if ((m_Link != nullptr) && !OutgoingData.empty())
+ {
+ m_Link->Send(OutgoingData.data(), OutgoingData.size());
+ }
- m_TimeSinceLastPacket += a_Dt;
- if (m_TimeSinceLastPacket > 30000.f) // 30 seconds time-out
+ m_TicksSinceLastPacket += 1;
+ if (m_TicksSinceLastPacket > 600) // 30 seconds time-out
{
SendDisconnect("Nooooo!! You timed out! D: Come back!");
Destroy();
@@ -1955,7 +1979,21 @@ void cClientHandle::ServerTick(float a_Dt)
cCSLock Lock(m_CSIncomingData);
std::swap(IncomingData, m_IncomingData);
}
- m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
+ if (!IncomingData.empty())
+ {
+ m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
+ }
+
+ // Send any queued outgoing data:
+ AString OutgoingData;
+ {
+ cCSLock Lock(m_CSOutgoingData);
+ std::swap(OutgoingData, m_OutgoingData);
+ }
+ if ((m_Link != nullptr) && !OutgoingData.empty())
+ {
+ m_Link->Send(OutgoingData.data(), OutgoingData.size());
+ }
if (m_State == csAuthenticated)
{
@@ -1970,8 +2008,8 @@ void cClientHandle::ServerTick(float a_Dt)
return;
}
- m_TimeSinceLastPacket += a_Dt;
- if (m_TimeSinceLastPacket > 30000.f) // 30 seconds time-out
+ m_TicksSinceLastPacket += 1;
+ if (m_TicksSinceLastPacket > 600) // 30 seconds
{
SendDisconnect("Nooooo!! You timed out! D: Come back!");
Destroy();
@@ -2843,94 +2881,79 @@ void cClientHandle::PacketError(UInt32 a_PacketType)
-bool cClientHandle::DataReceived(const char * a_Data, size_t a_Size)
+void cClientHandle::SocketClosed(void)
{
- // Data is received from the client, store it in the buffer to be processed by the Tick thread:
- m_TimeSinceLastPacket = 0;
- cCSLock Lock(m_CSIncomingData);
- m_IncomingData.append(a_Data, a_Size);
- return false;
+ // The socket has been closed for any reason
+
+ if (!m_Username.empty()) // Ignore client pings
+ {
+ LOGD("Client %s @ %s disconnected", m_Username.c_str(), m_IPString.c_str());
+ cRoot::Get()->GetPluginManager()->CallHookDisconnect(*this, "Player disconnected");
+ }
+
+ Destroy();
}
-void cClientHandle::GetOutgoingData(AString & a_Data)
+void cClientHandle::SetSelf(cClientHandlePtr a_Self)
{
- // Data can be sent to client
- {
- cCSLock Lock(m_CSOutgoingData);
- m_OutgoingData.ReadAll(a_Data);
- m_OutgoingData.CommitRead();
- a_Data.append(m_OutgoingDataOverflow);
- m_OutgoingDataOverflow.clear();
- }
-
- // Disconnect player after all packets have been sent
- if (m_HasSentDC && a_Data.empty())
- {
- Destroy();
- }
+ ASSERT(m_Self == nullptr);
+ m_Self = a_Self;
}
-void cClientHandle::SocketClosed(void)
+void cClientHandle::OnLinkCreated(cTCPLinkPtr a_Link)
{
- // The socket has been closed for any reason
-
- LOGD("Player %s @ %s disconnected", m_Username.c_str(), m_IPString.c_str());
+ m_Link = a_Link;
+}
- if (!m_Username.empty()) // Ignore client pings
- {
- cRoot::Get()->GetPluginManager()->CallHookDisconnect(*this, "Player disconnected");
- }
- Destroy();
+
+
+
+void cClientHandle::OnReceivedData(const char * a_Data, size_t a_Length)
+{
+ // Reset the timeout:
+ m_TicksSinceLastPacket = 0;
+
+ // Queue the incoming data to be processed in the tick thread:
+ cCSLock Lock(m_CSIncomingData);
+ m_IncomingData.append(a_Data, a_Length);
}
-void cClientHandle::HandleEnchantItem(Byte & a_WindowID, Byte & a_Enchantment)
+void cClientHandle::OnRemoteClosed(void)
{
- if (a_Enchantment > 2)
{
- LOGWARNING("%s attempt to crash the server with invalid enchanting selection!", GetUsername().c_str());
- Kick("Invalid enchanting!");
- return;
+ cCSLock Lock(m_CSOutgoingData);
+ m_Link.reset();
}
+ SocketClosed();
+}
- if (
- (m_Player->GetWindow() == nullptr) ||
- (m_Player->GetWindow()->GetWindowID() != a_WindowID) ||
- (m_Player->GetWindow()->GetWindowType() != cWindow::wtEnchantment)
- )
- {
- return;
- }
-
- cEnchantingWindow * Window = (cEnchantingWindow*) m_Player->GetWindow();
- cItem Item = *Window->m_SlotArea->GetSlot(0, *m_Player);
- int BaseEnchantmentLevel = Window->GetPropertyValue(a_Enchantment);
- if (Item.EnchantByXPLevels(BaseEnchantmentLevel))
- {
- if (m_Player->IsGameModeCreative() || m_Player->DeltaExperience(-m_Player->XpForLevel(BaseEnchantmentLevel)) >= 0)
- {
- Window->m_SlotArea->SetSlot(0, *m_Player, Item);
- Window->SendSlot(*m_Player, Window->m_SlotArea, 0);
- Window->BroadcastWholeWindow();
- Window->SetProperty(0, 0, *m_Player);
- Window->SetProperty(1, 0, *m_Player);
- Window->SetProperty(2, 0, *m_Player);
- }
+
+
+void cClientHandle::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
+{
+ LOGD("An error has occurred on client link for %s @ %s: %d (%s). Client disconnected.",
+ m_Username.c_str(), m_IPString.c_str(), a_ErrorCode, a_ErrorMsg.c_str()
+ );
+ {
+ cCSLock Lock(m_CSOutgoingData);
+ m_Link.reset();
}
+ SocketClosed();
}
diff --git a/src/ClientHandle.h b/src/ClientHandle.h
index 03ae38cfd..8129d6a50 100644
--- a/src/ClientHandle.h
+++ b/src/ClientHandle.h
@@ -8,12 +8,10 @@
#pragma once
-#ifndef CCLIENTHANDLE_H_INCLUDED
-#define CCLIENTHANDLE_H_INCLUDED
+#include "OSSupport/Network.h"
#include "Defines.h"
#include "Vector3.h"
-#include "OSSupport/SocketThreads.h"
#include "ChunkDef.h"
#include "ByteBuffer.h"
#include "Scoreboard.h"
@@ -27,6 +25,7 @@
+// fwd:
class cChunkDataSerializer;
class cInventory;
class cMonster;
@@ -42,25 +41,29 @@ class cItemHandler;
class cWorld;
class cCompositeChat;
class cStatManager;
+class cClientHandle;
+typedef SharedPtr<cClientHandle> cClientHandlePtr;
-class cClientHandle : // tolua_export
- public cSocketThreads::cCallback
+class cClientHandle // tolua_export
+ : public cTCPLink::cCallbacks
{ // tolua_export
-public:
-
-#if defined(ANDROID_NDK)
- static const int DEFAULT_VIEW_DISTANCE = 4; // The default ViewDistance (used when no value is set in Settings.ini)
-#else
- static const int DEFAULT_VIEW_DISTANCE = 10;
-#endif
+public: // tolua_export
+
+ #if defined(ANDROID_NDK)
+ static const int DEFAULT_VIEW_DISTANCE = 4; // The default ViewDistance (used when no value is set in Settings.ini)
+ #else
+ static const int DEFAULT_VIEW_DISTANCE = 10;
+ #endif
static const int MAX_VIEW_DISTANCE = 32;
static const int MIN_VIEW_DISTANCE = 1;
- cClientHandle(const cSocket * a_Socket, int a_ViewDistance);
+ /** Creates a new client with the specified IP address in its description and the specified initial view distance. */
+ cClientHandle(const AString & a_IPString, int a_ViewDistance);
+
virtual ~cClientHandle();
const AString & GetIPString(void) const { return m_IPString; } // tolua_export
@@ -276,6 +279,10 @@ public:
void HandleCommandBlockEntityChange(int a_EntityID, const AString & a_NewCommand);
void HandleCreativeInventory (short a_SlotNum, const cItem & a_HeldItem);
+
+ /** Called when the player enchants an Item in the Enchanting table UI. */
+ void HandleEnchantItem(Byte a_WindowID, Byte a_Enchantment);
+
void HandleEntityCrouch (int a_EntityID, bool a_IsCrouching);
void HandleEntityLeaveBed (int a_EntityID);
void HandleEntitySprinting (int a_EntityID, bool a_IsSprinting);
@@ -329,9 +336,6 @@ public:
Sends an UnloadChunk packet for each loaded chunk and resets the streamed chunks. */
void RemoveFromWorld(void);
- /** Called when the player will enchant a Item */
- void HandleEnchantItem(Byte & a_WindowID, Byte & a_Enchantment);
-
/** Called by the protocol recognizer when the protocol version is known. */
void SetProtocolVersion(UInt32 a_ProtocolVersion) { m_ProtocolVersion = a_ProtocolVersion; }
@@ -340,6 +344,9 @@ public:
private:
+ friend class cServer; // Needs access to SetSelf()
+
+
/** The type used for storing the names of registered plugin channels. */
typedef std::set<AString> cChannels;
@@ -361,13 +368,20 @@ private:
cChunkCoordsList m_SentChunks; // Chunks that are currently sent to the client
cProtocol * m_Protocol;
-
+
+ /** Protects m_IncomingData against multithreaded access. */
cCriticalSection m_CSIncomingData;
- AString m_IncomingData;
-
+
+ /** Queue for the incoming data received on the link until it is processed in Tick().
+ Protected by m_CSIncomingData. */
+ AString m_IncomingData;
+
+ /** Protects m_OutgoingData against multithreaded access. */
cCriticalSection m_CSOutgoingData;
- cByteBuffer m_OutgoingData;
- AString m_OutgoingDataOverflow; ///< For data that didn't fit into the m_OutgoingData ringbuffer temporarily
+
+ /** Buffer for storing outgoing data from any thread; will get sent in Tick() (to prevent deadlocks).
+ Protected by m_CSOutgoingData. */
+ AString m_OutgoingData;
Vector3d m_ConfirmPosition;
@@ -379,8 +393,8 @@ private:
int m_LastStreamedChunkX;
int m_LastStreamedChunkZ;
- /** Seconds since the last packet data was received (updated in Tick(), reset in DataReceived()) */
- float m_TimeSinceLastPacket;
+ /** Number of ticks since the last network packet was received (increased in Tick(), reset in OnReceivedData()) */
+ int m_TicksSinceLastPacket;
/** Duration of the last completed client ping. */
std::chrono::steady_clock::duration m_Ping;
@@ -458,6 +472,13 @@ private:
/** The version of the protocol that the client is talking, or 0 if unknown. */
UInt32 m_ProtocolVersion;
+ /** The link that is used for network communication.
+ m_CSOutgoingData is used to synchronize access for sending data. */
+ cTCPLinkPtr m_Link;
+
+ /** Shared pointer to self, so that this instance can keep itself alive when needed. */
+ cClientHandlePtr m_Self;
+
/** Returns true if the rate block interactions is within a reasonable limit (bot protection) */
bool CheckBlockInteractionsRate(void);
@@ -483,16 +504,19 @@ private:
/** Removes all of the channels from the list of current plugin channels. Ignores channels that are not found. */
void UnregisterPluginChannels(const AStringVector & a_ChannelList);
- // cSocketThreads::cCallback overrides:
- virtual bool DataReceived (const char * a_Data, size_t a_Size) override; // Data is received from the client
- virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client
- virtual void SocketClosed (void) override; // The socket has been closed for any reason
-}; // tolua_export
-
+ /** Called when the network socket has been closed. */
+ void SocketClosed(void);
+ /** Called right after the instance is created to store its SharedPtr inside. */
+ void SetSelf(cClientHandlePtr a_Self);
+ // cTCPLink::cCallbacks overrides:
+ virtual void OnLinkCreated(cTCPLinkPtr a_Link) override;
+ virtual void OnReceivedData(const char * a_Data, size_t a_Length) override;
+ virtual void OnRemoteClosed(void) override;
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
+}; // tolua_export
-#endif // CCLIENTHANDLE_H_INCLUDED
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index 1ca131375..527380761 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -47,7 +47,7 @@ const int cPlayer::EATING_TICKS = 30;
-cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) :
+cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) :
super(etPlayer, 0.6, 1.8),
m_bVisible(true),
m_FoodLevel(MAX_FOOD_LEVEL),
@@ -174,7 +174,7 @@ void cPlayer::Destroyed()
void cPlayer::SpawnOn(cClientHandle & a_Client)
{
- if (!m_bVisible || (m_ClientHandle == (&a_Client)))
+ if (!m_bVisible || (m_ClientHandle.get() == (&a_Client)))
{
return;
}
@@ -246,7 +246,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
if (CanMove)
{
- BroadcastMovementUpdate(m_ClientHandle);
+ BroadcastMovementUpdate(m_ClientHandle.get());
}
if (m_Health > 0) // make sure player is alive
@@ -419,7 +419,7 @@ void cPlayer::StartChargingBow(void)
LOGD("Player \"%s\" started charging their bow", GetName().c_str());
m_IsChargingBow = true;
m_BowCharge = 0;
- m_World->BroadcastEntityMetadata(*this, m_ClientHandle);
+ m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
}
@@ -432,7 +432,7 @@ int cPlayer::FinishChargingBow(void)
int res = m_BowCharge;
m_IsChargingBow = false;
m_BowCharge = 0;
- m_World->BroadcastEntityMetadata(*this, m_ClientHandle);
+ m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
return res;
}
@@ -446,7 +446,7 @@ void cPlayer::CancelChargingBow(void)
LOGD("Player \"%s\" cancelled charging their bow at a charge of %d", GetName().c_str(), m_BowCharge);
m_IsChargingBow = false;
m_BowCharge = 0;
- m_World->BroadcastEntityMetadata(*this, m_ClientHandle);
+ m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
}
@@ -1391,7 +1391,7 @@ void cPlayer::SetVisible(bool a_bVisible)
if (!a_bVisible && m_bVisible)
{
m_bVisible = false;
- m_World->BroadcastDestroyEntity(*this, m_ClientHandle); // Destroy on all clients
+ m_World->BroadcastDestroyEntity(*this, m_ClientHandle.get()); // Destroy on all clients
}
}
@@ -2294,6 +2294,16 @@ void cPlayer::Detach()
+void cPlayer::RemoveClientHandle(void)
+{
+ ASSERT(m_ClientHandle != nullptr);
+ m_ClientHandle.reset();
+}
+
+
+
+
+
AString cPlayer::GetUUIDFileName(const AString & a_UUID)
{
AString UUID = cMojangAPI::MakeUUIDDashed(a_UUID);
diff --git a/src/Entities/Player.h b/src/Entities/Player.h
index d3ed46db6..fa9ac7cad 100644
--- a/src/Entities/Player.h
+++ b/src/Entities/Player.h
@@ -40,7 +40,7 @@ public:
CLASS_PROTODEF(cPlayer)
- cPlayer(cClientHandle * a_Client, const AString & a_PlayerName);
+ cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName);
virtual ~cPlayer();
@@ -222,7 +222,15 @@ public:
/** Closes the current window if it matches the specified ID, resets current window to m_InventoryWindow */
void CloseWindowIfID(char a_WindowID, bool a_CanRefuse = true);
- cClientHandle * GetClientHandle(void) const { return m_ClientHandle; }
+ /** Returns the raw client handle associated with the player. */
+ cClientHandle * GetClientHandle(void) const { return m_ClientHandle.get(); }
+
+ // tolua_end
+
+ /** Returns the SharedPtr to client handle associated with the player. */
+ cClientHandlePtr GetClientHandlePtr(void) const { return m_ClientHandle; }
+
+ // tolua_begin
void SendMessage (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtCustom); }
void SendMessageInfo (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtInformation); }
@@ -467,6 +475,10 @@ public:
virtual bool IsRclking (void) const { return IsEating() || IsChargingBow(); }
virtual void Detach(void);
+
+ /** Called by cClientHandle when the client is being destroyed.
+ The player removes its m_ClientHandle ownership so that the ClientHandle gets deleted. */
+ void RemoveClientHandle(void);
protected:
@@ -537,7 +549,7 @@ protected:
std::chrono::steady_clock::time_point m_LastPlayerListTime;
- cClientHandle * m_ClientHandle;
+ cClientHandlePtr m_ClientHandle;
cSlotNums m_InventoryPaintSlots;
diff --git a/src/Generating/BioGen.cpp b/src/Generating/BioGen.cpp
index 2cc810d3b..378ece6a3 100644
--- a/src/Generating/BioGen.cpp
+++ b/src/Generating/BioGen.cpp
@@ -205,8 +205,7 @@ void cBiomeGenList::InitializeBiomes(const AString & a_Biomes)
int Count = 1;
if (Split2.size() >= 2)
{
- Count = atol(Split2[1].c_str());
- if (Count <= 0)
+ if (!StringToInteger(Split2[1], Count))
{
LOGWARNING("Cannot decode biome count: \"%s\"; using 1.", Split2[1].c_str());
Count = 1;
diff --git a/src/HTTPServer/HTTPConnection.cpp b/src/HTTPServer/HTTPConnection.cpp
index d5dbf0199..de12b36ce 100644
--- a/src/HTTPServer/HTTPConnection.cpp
+++ b/src/HTTPServer/HTTPConnection.cpp
@@ -38,8 +38,7 @@ cHTTPConnection::~cHTTPConnection()
void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response)
{
- AppendPrintf(m_OutgoingData, "%d %s\r\nContent-Length: 0\r\n\r\n", a_StatusCode, a_Response.c_str());
- m_HTTPServer.NotifyConnectionWrite(*this);
+ SendData(Printf("%d %s\r\nContent-Length: 0\r\n\r\n", a_StatusCode, a_Response.c_str()));
m_State = wcsRecvHeaders;
}
@@ -49,8 +48,7 @@ void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Re
void cHTTPConnection::SendNeedAuth(const AString & a_Realm)
{
- AppendPrintf(m_OutgoingData, "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str());
- m_HTTPServer.NotifyConnectionWrite(*this);
+ SendData(Printf("HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str()));
m_State = wcsRecvHeaders;
}
@@ -61,9 +59,10 @@ void cHTTPConnection::SendNeedAuth(const AString & a_Realm)
void cHTTPConnection::Send(const cHTTPResponse & a_Response)
{
ASSERT(m_State == wcsRecvIdle);
- a_Response.AppendToData(m_OutgoingData);
+ AString toSend;
+ a_Response.AppendToData(toSend);
m_State = wcsSendingResp;
- m_HTTPServer.NotifyConnectionWrite(*this);
+ SendData(toSend);
}
@@ -73,10 +72,10 @@ void cHTTPConnection::Send(const cHTTPResponse & a_Response)
void cHTTPConnection::Send(const void * a_Data, size_t a_Size)
{
ASSERT(m_State == wcsSendingResp);
- AppendPrintf(m_OutgoingData, SIZE_T_FMT_HEX "\r\n", a_Size);
- m_OutgoingData.append((const char *)a_Data, a_Size);
- m_OutgoingData.append("\r\n");
- m_HTTPServer.NotifyConnectionWrite(*this);
+ // We're sending in Chunked transfer encoding
+ SendData(Printf(SIZE_T_FMT_HEX "\r\n", a_Size));
+ SendData(a_Data, a_Size);
+ SendData("\r\n");
}
@@ -86,9 +85,8 @@ void cHTTPConnection::Send(const void * a_Data, size_t a_Size)
void cHTTPConnection::FinishResponse(void)
{
ASSERT(m_State == wcsSendingResp);
- m_OutgoingData.append("0\r\n\r\n");
+ SendData("0\r\n\r\n");
m_State = wcsRecvHeaders;
- m_HTTPServer.NotifyConnectionWrite(*this);
}
@@ -108,8 +106,7 @@ void cHTTPConnection::AwaitNextRequest(void)
case wcsRecvIdle:
{
// The client is waiting for a response, send an "Internal server error":
- m_OutgoingData.append("HTTP/1.1 500 Internal Server Error\r\n\r\n");
- m_HTTPServer.NotifyConnectionWrite(*this);
+ SendData("HTTP/1.1 500 Internal Server Error\r\n\r\n");
m_State = wcsRecvHeaders;
break;
}
@@ -117,7 +114,7 @@ void cHTTPConnection::AwaitNextRequest(void)
case wcsSendingResp:
{
// The response headers have been sent, we need to terminate the response body:
- m_OutgoingData.append("0\r\n\r\n");
+ SendData("0\r\n\r\n");
m_State = wcsRecvHeaders;
break;
}
@@ -140,15 +137,27 @@ void cHTTPConnection::Terminate(void)
{
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
}
- m_HTTPServer.CloseConnection(*this);
+ m_Link.reset();
}
-bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
+void cHTTPConnection::OnLinkCreated(cTCPLinkPtr a_Link)
{
+ ASSERT(m_Link == nullptr);
+ m_Link = a_Link;
+}
+
+
+
+
+
+void cHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size)
+{
+ ASSERT(m_Link != nullptr);
+
switch (m_State)
{
case wcsRecvHeaders:
@@ -164,13 +173,14 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
delete m_CurrentRequest;
m_CurrentRequest = nullptr;
m_State = wcsInvalid;
- m_HTTPServer.CloseConnection(*this);
- return true;
+ m_Link->Close();
+ m_Link.reset();
+ return;
}
if (m_CurrentRequest->IsInHeaders())
{
// The request headers are not yet complete
- return false;
+ return;
}
// The request has finished parsing its headers successfully, notify of it:
@@ -186,11 +196,13 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
// Process the rest of the incoming data into the request body:
if (a_Size > BytesConsumed)
{
- return cHTTPConnection::DataReceived(a_Data + BytesConsumed, a_Size - BytesConsumed);
+ cHTTPConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed);
+ return;
}
else
{
- return cHTTPConnection::DataReceived("", 0); // If the request has zero body length, let it be processed right-away
+ cHTTPConnection::OnReceivedData("", 0); // If the request has zero body length, let it be processed right-away
+ return;
}
}
@@ -210,8 +222,9 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
if (!m_CurrentRequest->DoesAllowKeepAlive())
{
m_State = wcsInvalid;
- m_HTTPServer.CloseConnection(*this);
- return true;
+ m_Link->Close();
+ m_Link.reset();
+ return;
}
delete m_CurrentRequest;
m_CurrentRequest = nullptr;
@@ -225,32 +238,39 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
break;
}
}
- return false;
}
-void cHTTPConnection::GetOutgoingData(AString & a_Data)
+void cHTTPConnection::OnRemoteClosed(void)
{
- std::swap(a_Data, m_OutgoingData);
+ if (m_CurrentRequest != nullptr)
+ {
+ m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
+ }
+ m_Link.reset();
}
-void cHTTPConnection::SocketClosed(void)
+
+void cHTTPConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
{
- if (m_CurrentRequest != nullptr)
- {
- m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
- }
- m_HTTPServer.CloseConnection(*this);
+ OnRemoteClosed();
}
+void cHTTPConnection::SendData(const void * a_Data, size_t a_Size)
+{
+ m_Link->Send(a_Data, a_Size);
+}
+
+
+
diff --git a/src/HTTPServer/HTTPConnection.h b/src/HTTPServer/HTTPConnection.h
index ccbf26466..8ecc4a4d4 100644
--- a/src/HTTPServer/HTTPConnection.h
+++ b/src/HTTPServer/HTTPConnection.h
@@ -9,7 +9,7 @@
#pragma once
-#include "../OSSupport/SocketThreads.h"
+#include "../OSSupport/Network.h"
@@ -25,7 +25,7 @@ class cHTTPRequest;
class cHTTPConnection :
- public cSocketThreads::cCallback
+ public cTCPLink::cCallbacks
{
public:
@@ -78,9 +78,6 @@ protected:
/** Status in which the request currently is */
eState m_State;
- /** Data that is queued for sending, once the socket becomes writable */
- AString m_OutgoingData;
-
/** The request being currently received
Valid only between having parsed the headers and finishing receiving the body. */
cHTTPRequest * m_CurrentRequest;
@@ -88,18 +85,34 @@ protected:
/** Number of bytes that remain to read for the complete body of the message to be received.
Valid only in wcsRecvBody */
size_t m_CurrentRequestBodyRemaining;
+
+ /** The network link attached to this connection. */
+ cTCPLinkPtr m_Link;
- // cSocketThreads::cCallback overrides:
- /** Data is received from the client.
- Returns true if the connection has been closed as the result of parsing the data. */
- virtual bool DataReceived(const char * a_Data, size_t a_Size) override;
+ // cTCPLink::cCallbacks overrides:
+ /** The link instance has been created, remember it. */
+ virtual void OnLinkCreated(cTCPLinkPtr a_Link) override;
+
+ /** Data is received from the client. */
+ virtual void OnReceivedData(const char * a_Data, size_t a_Size) override;
- /** Data can be sent to client */
- virtual void GetOutgoingData(AString & a_Data) override;
+ /** The socket has been closed for any reason. */
+ virtual void OnRemoteClosed(void) override;
- /** The socket has been closed for any reason */
- virtual void SocketClosed(void) override;
+ /** An error has occurred on the socket. */
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
+
+ // Overridable:
+ /** Called to send raw data over the link. Descendants may provide data transformations (SSL etc.) */
+ virtual void SendData(const void * a_Data, size_t a_Size);
+
+ /** Sends the raw data over the link.
+ Descendants may provide data transformations (SSL etc.) via the overridable SendData() function. */
+ void SendData(const AString & a_Data)
+ {
+ SendData(a_Data.data(), a_Data.size());
+ }
} ;
typedef std::vector<cHTTPConnection *> cHTTPConnections;
diff --git a/src/HTTPServer/HTTPMessage.cpp b/src/HTTPServer/HTTPMessage.cpp
index d59ca438e..c87b3cc8b 100644
--- a/src/HTTPServer/HTTPMessage.cpp
+++ b/src/HTTPServer/HTTPMessage.cpp
@@ -55,7 +55,10 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value)
}
else if (Key == "content-length")
{
- m_ContentLength = static_cast<size_t>(atol(m_Headers[Key].c_str()));
+ if (!StringToInteger(m_Headers[Key], m_ContentLength))
+ {
+ m_ContentLength = 0;
+ }
}
}
diff --git a/src/HTTPServer/HTTPServer.cpp b/src/HTTPServer/HTTPServer.cpp
index 9ab030a1f..71f974a97 100644
--- a/src/HTTPServer/HTTPServer.cpp
+++ b/src/HTTPServer/HTTPServer.cpp
@@ -119,11 +119,45 @@ class cDebugCallbacks :
////////////////////////////////////////////////////////////////////////////////
+// cHTTPServerListenCallbacks:
+
+class cHTTPServerListenCallbacks:
+ public cNetwork::cListenCallbacks
+{
+public:
+ cHTTPServerListenCallbacks(cHTTPServer & a_HTTPServer, UInt16 a_Port):
+ m_HTTPServer(a_HTTPServer),
+ m_Port(a_Port)
+ {
+ }
+
+protected:
+ /** The HTTP server instance that we're attached to. */
+ cHTTPServer & m_HTTPServer;
+
+ /** The port for which this instance is responsible. */
+ UInt16 m_Port;
+
+ // cNetwork::cListenCallbacks overrides:
+ virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override
+ {
+ return m_HTTPServer.OnIncomingConnection(a_RemoteIPAddress, a_RemotePort);
+ }
+ virtual void OnAccepted(cTCPLink & a_Link) override {}
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
+ {
+ LOGWARNING("HTTP server error on port %d: %d (%s)", m_Port, a_ErrorCode, a_ErrorMsg.c_str());
+ }
+};
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
// cHTTPServer:
cHTTPServer::cHTTPServer(void) :
- m_ListenThreadIPv4(*this, cSocket::IPv4, "WebServer"),
- m_ListenThreadIPv6(*this, cSocket::IPv6, "WebServer"),
m_Callbacks(nullptr)
{
}
@@ -141,7 +175,7 @@ cHTTPServer::~cHTTPServer()
-bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6)
+bool cHTTPServer::Initialize(void)
{
// Read the HTTPS cert + key:
AString CertFile = cFile::ReadWholeFile("webadmin/httpscert.crt");
@@ -177,18 +211,6 @@ bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_Port
{
LOGINFO("WebServer: The server is running in secure HTTPS mode.");
}
-
- // Open up requested ports:
- bool HasAnyPort;
- m_ListenThreadIPv4.SetReuseAddr(true);
- m_ListenThreadIPv6.SetReuseAddr(true);
- HasAnyPort = m_ListenThreadIPv4.Initialize(a_PortsIPv4);
- HasAnyPort = m_ListenThreadIPv6.Initialize(a_PortsIPv6) || HasAnyPort;
- if (!HasAnyPort)
- {
- return false;
- }
-
return true;
}
@@ -196,19 +218,28 @@ bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_Port
-bool cHTTPServer::Start(cCallbacks & a_Callbacks)
+bool cHTTPServer::Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports)
{
m_Callbacks = &a_Callbacks;
- if (!m_ListenThreadIPv4.Start())
- {
- return false;
- }
- if (!m_ListenThreadIPv6.Start())
+
+ // Open up requested ports:
+ for (auto port : a_Ports)
{
- m_ListenThreadIPv4.Stop();
- return false;
- }
- return true;
+ UInt16 PortNum;
+ if (!StringToInteger(port, PortNum))
+ {
+ LOGWARNING("WebServer: Invalid port value: \"%s\". Ignoring.", port.c_str());
+ continue;
+ }
+ auto Handle = cNetwork::Listen(PortNum, std::make_shared<cHTTPServerListenCallbacks>(*this, PortNum));
+ if (Handle->IsListening())
+ {
+ m_ServerHandles.push_back(Handle);
+ }
+ } // for port - a_Ports[]
+
+ // Report success if at least one port opened successfully:
+ return !m_ServerHandles.empty();
}
@@ -217,63 +248,30 @@ bool cHTTPServer::Start(cCallbacks & a_Callbacks)
void cHTTPServer::Stop(void)
{
- m_ListenThreadIPv4.Stop();
- m_ListenThreadIPv6.Stop();
-
- // Drop all current connections:
- cCSLock Lock(m_CSConnections);
- while (!m_Connections.empty())
+ for (auto handle : m_ServerHandles)
{
- m_Connections.front()->Terminate();
- } // for itr - m_Connections[]
+ handle->Close();
+ }
+ m_ServerHandles.clear();
}
-void cHTTPServer::OnConnectionAccepted(cSocket & a_Socket)
+cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort)
{
- cHTTPConnection * Connection;
+ UNUSED(a_RemoteIPAddress);
+ UNUSED(a_RemotePort);
+
if (m_Cert.get() != nullptr)
{
- Connection = new cSslHTTPConnection(*this, m_Cert, m_CertPrivKey);
+ return std::make_shared<cSslHTTPConnection>(*this, m_Cert, m_CertPrivKey);
}
else
{
- Connection = new cHTTPConnection(*this);
+ return std::make_shared<cHTTPConnection>(*this);
}
- m_SocketThreads.AddClient(a_Socket, Connection);
- cCSLock Lock(m_CSConnections);
- m_Connections.push_back(Connection);
-}
-
-
-
-
-
-void cHTTPServer::CloseConnection(cHTTPConnection & a_Connection)
-{
- m_SocketThreads.RemoveClient(&a_Connection);
- cCSLock Lock(m_CSConnections);
- for (cHTTPConnections::iterator itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr)
- {
- if (*itr == &a_Connection)
- {
- m_Connections.erase(itr);
- break;
- }
- }
- delete &a_Connection;
-}
-
-
-
-
-
-void cHTTPServer::NotifyConnectionWrite(cHTTPConnection & a_Connection)
-{
- m_SocketThreads.NotifyWrite(&a_Connection);
}
diff --git a/src/HTTPServer/HTTPServer.h b/src/HTTPServer/HTTPServer.h
index 73d4cbdd0..d626fb475 100644
--- a/src/HTTPServer/HTTPServer.h
+++ b/src/HTTPServer/HTTPServer.h
@@ -9,8 +9,7 @@
#pragma once
-#include "../OSSupport/ListenThread.h"
-#include "../OSSupport/SocketThreads.h"
+#include "../OSSupport/Network.h"
#include "../IniFile.h"
#include "PolarSSL++/RsaPrivateKey.h"
#include "PolarSSL++/CryptoKey.h"
@@ -33,8 +32,7 @@ typedef std::vector<cHTTPConnection *> cHTTPConnections;
-class cHTTPServer :
- public cListenThread::cCallback
+class cHTTPServer
{
public:
class cCallbacks
@@ -42,44 +40,39 @@ public:
public:
virtual ~cCallbacks() {}
- /** Called when a new request arrives over a connection and its headers have been parsed.
- The request body needn't have arrived yet.
- */
+ /** Called when a new request arrives over a connection and all its headers have been parsed.
+ The request body needn't have arrived yet. */
virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0;
/** Called when another part of request body has arrived.
May be called multiple times for a single request. */
virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) = 0;
- /// Called when the request body has been fully received in previous calls to OnRequestBody()
+ /** Called when the request body has been fully received in previous calls to OnRequestBody() */
virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0;
} ;
cHTTPServer(void);
virtual ~cHTTPServer();
- /// Initializes the server on the specified ports
- bool Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6);
+ /** Initializes the server - reads the cert files etc. */
+ bool Initialize(void);
- /// Starts the server and assigns the callbacks to use for incoming requests
- bool Start(cCallbacks & a_Callbacks);
+ /** Starts the server and assigns the callbacks to use for incoming requests */
+ bool Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports);
- /// Stops the server, drops all current connections
+ /** Stops the server, drops all current connections */
void Stop(void);
protected:
friend class cHTTPConnection;
friend class cSslHTTPConnection;
+ friend class cHTTPServerListenCallbacks;
- cListenThread m_ListenThreadIPv4;
- cListenThread m_ListenThreadIPv6;
+ /** The cNetwork API handle for the listening socket. */
+ cServerHandlePtrs m_ServerHandles;
- cSocketThreads m_SocketThreads;
-
- cCriticalSection m_CSConnections;
- cHTTPConnections m_Connections; ///< All the connections that are currently being serviced
-
- /// The callbacks to call for various events
+ /** The callbacks to call for various events */
cCallbacks * m_Callbacks;
/** The server certificate to use for the SSL connections */
@@ -89,23 +82,18 @@ protected:
cCryptoKeyPtr m_CertPrivKey;
- // cListenThread::cCallback overrides:
- virtual void OnConnectionAccepted(cSocket & a_Socket) override;
-
- /// Called by cHTTPConnection to close the connection (presumably due to an error)
- void CloseConnection(cHTTPConnection & a_Connection);
-
- /// Called by cHTTPConnection to notify SocketThreads that there's data to be sent for the connection
- void NotifyConnectionWrite(cHTTPConnection & a_Connection);
-
- /// Called by cHTTPConnection when it finishes parsing the request header
+ /** Called by cHTTPServerListenCallbacks when there's a new incoming connection.
+ Returns the connection instance to be used as the cTCPLink callbacks. */
+ cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort);
+
+ /** Called by cHTTPConnection when it finishes parsing the request header */
void NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
/** Called by cHTTPConenction when it receives more data for the request body.
May be called multiple times for a single request. */
void RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size);
- /// Called by cHTTPConnection when it detects that the request has finished (all of its body has been received)
+ /** Called by cHTTPConnection when it detects that the request has finished (all of its body has been received) */
void RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
} ;
diff --git a/src/HTTPServer/SslHTTPConnection.cpp b/src/HTTPServer/SslHTTPConnection.cpp
index d237089d9..f09daac8f 100644
--- a/src/HTTPServer/SslHTTPConnection.cpp
+++ b/src/HTTPServer/SslHTTPConnection.cpp
@@ -25,14 +25,8 @@ cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509Ce
-bool cSslHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
+void cSslHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size)
{
- // If there is outgoing data in the queue, notify the server that it should write it out:
- if (!m_OutgoingData.empty())
- {
- m_HTTPServer.NotifyConnectionWrite(*this);
- }
-
// Process the received data:
const char * Data = a_Data;
size_t Size = a_Size;
@@ -52,17 +46,18 @@ bool cSslHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
int NumRead = m_Ssl.ReadPlain(Buffer, sizeof(Buffer));
if (NumRead > 0)
{
- if (super::DataReceived(Buffer, (size_t)NumRead))
- {
- // The socket has been closed, and the object is already deleted. Bail out.
- return true;
- }
+ super::OnReceivedData(Buffer, (size_t)NumRead);
+ }
+ else if (NumRead == POLARSSL_ERR_NET_WANT_READ)
+ {
+ // SSL requires us to send data to peer first, do so by "sending" empty data:
+ SendData(nullptr, 0);
}
// If both failed, bail out:
if ((BytesWritten == 0) && (NumRead <= 0))
{
- return false;
+ return;
}
}
}
@@ -71,18 +66,20 @@ bool cSslHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
-void cSslHTTPConnection::GetOutgoingData(AString & a_Data)
+void cSslHTTPConnection::SendData(const void * a_Data, size_t a_Size)
{
+ const char * OutgoingData = reinterpret_cast<const char *>(a_Data);
+ size_t pos = 0;
for (;;)
{
// Write as many bytes from our buffer to SSL's encryption as possible:
int NumWritten = 0;
- if (!m_OutgoingData.empty())
+ if (pos < a_Size)
{
- NumWritten = m_Ssl.WritePlain(m_OutgoingData.data(), m_OutgoingData.size());
+ NumWritten = m_Ssl.WritePlain(OutgoingData + pos, a_Size - pos);
if (NumWritten > 0)
{
- m_OutgoingData.erase(0, (size_t)NumWritten);
+ pos += static_cast<size_t>(NumWritten);
}
}
@@ -91,7 +88,7 @@ void cSslHTTPConnection::GetOutgoingData(AString & a_Data)
size_t NumBytes = m_Ssl.ReadOutgoing(Buffer, sizeof(Buffer));
if (NumBytes > 0)
{
- a_Data.append(Buffer, NumBytes);
+ m_Link->Send(Buffer, NumBytes);
}
// If both failed, bail out:
diff --git a/src/HTTPServer/SslHTTPConnection.h b/src/HTTPServer/SslHTTPConnection.h
index c2c1585cd..dc54b1eff 100644
--- a/src/HTTPServer/SslHTTPConnection.h
+++ b/src/HTTPServer/SslHTTPConnection.h
@@ -36,8 +36,8 @@ protected:
cCryptoKeyPtr m_PrivateKey;
// cHTTPConnection overrides:
- virtual bool DataReceived (const char * a_Data, size_t a_Size) override; // Data is received from the client
- virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client
+ virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; // Data is received from the client
+ virtual void SendData(const void * a_Data, size_t a_Size) override; // Data is to be sent to client
} ;
diff --git a/src/IniFile.cpp b/src/IniFile.cpp
index ded7e4199..3a213a90e 100644
--- a/src/IniFile.cpp
+++ b/src/IniFile.cpp
@@ -888,3 +888,39 @@ void cIniFile::RemoveBom(AString & a_line) const
+
+AStringVector ReadUpgradeIniPorts(
+ cIniFile & a_IniFile,
+ const AString & a_KeyName,
+ const AString & a_PortsValueName,
+ const AString & a_OldIPv4ValueName,
+ const AString & a_OldIPv6ValueName,
+ const AString & a_DefaultValue
+)
+{
+ // Read the regular value, but don't use the default (in order to detect missing value for upgrade):
+ AStringVector Ports = StringSplitAndTrim(a_IniFile.GetValue(a_KeyName, a_PortsValueName), ";,");
+
+ if (Ports.empty())
+ {
+ // Historically there were two separate entries for IPv4 and IPv6, merge them and migrate:
+ AString Ports4 = a_IniFile.GetValue(a_KeyName, a_OldIPv4ValueName, a_DefaultValue);
+ AString Ports6 = a_IniFile.GetValue(a_KeyName, a_OldIPv6ValueName);
+ Ports = MergeStringVectors(StringSplitAndTrim(Ports4, ";,"), StringSplitAndTrim(Ports6, ";,"));
+ a_IniFile.DeleteValue(a_KeyName, a_OldIPv4ValueName);
+ a_IniFile.DeleteValue(a_KeyName, a_OldIPv6ValueName);
+
+ // If those weren't present or were empty, use the default:"
+ if (Ports.empty())
+ {
+ Ports = StringSplitAndTrim(a_DefaultValue, ";,");
+ }
+ a_IniFile.SetValue(a_KeyName, a_PortsValueName, StringsConcat(Ports, ','));
+ }
+
+ return Ports;
+}
+
+
+
+
diff --git a/src/IniFile.h b/src/IniFile.h
index e5879f46c..3e717723f 100644
--- a/src/IniFile.h
+++ b/src/IniFile.h
@@ -15,9 +15,7 @@
!! MODIFIED BY FAKETRUTH and madmaxoft!!
*/
-#ifndef CIniFile_H
-#define CIniFile_H
-
+#pragma once
@@ -215,4 +213,22 @@ public:
// tolua_end
-#endif
+
+
+
+
+/** Reads the list of ports from the INI file, possibly upgrading from IPv4/IPv6-specific values into new version-agnostic value.
+Reads the list of ports from a_PortsValueName. If that value doesn't exist or is empty, the list is combined from values
+in a_OldIPv4ValueName and a_OldIPv6ValueName; in this case the old values are removed from the INI file.
+If there is none of the three values or they are all empty, the default is used and stored in the Ports value. */
+AStringVector ReadUpgradeIniPorts(
+ cIniFile & a_IniFile,
+ const AString & a_KeyName,
+ const AString & a_PortsValueName,
+ const AString & a_OldIPv4ValueName,
+ const AString & a_OldIPv6ValueName,
+ const AString & a_DefaultValue
+);
+
+
+
diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt
index c8e96cbec..6f609c519 100644
--- a/src/OSSupport/CMakeLists.txt
+++ b/src/OSSupport/CMakeLists.txt
@@ -13,12 +13,9 @@ SET (SRCS
HostnameLookup.cpp
IPLookup.cpp
IsThread.cpp
- ListenThread.cpp
NetworkSingleton.cpp
Semaphore.cpp
ServerHandleImpl.cpp
- Socket.cpp
- SocketThreads.cpp
StackTrace.cpp
TCPLinkImpl.cpp
)
@@ -32,14 +29,11 @@ SET (HDRS
HostnameLookup.h
IPLookup.h
IsThread.h
- ListenThread.h
Network.h
NetworkSingleton.h
Queue.h
Semaphore.h
ServerHandleImpl.h
- Socket.h
- SocketThreads.h
StackTrace.h
TCPLinkImpl.h
)
diff --git a/src/OSSupport/File.h b/src/OSSupport/File.h
index dfb38e839..ac6d1ab21 100644
--- a/src/OSSupport/File.h
+++ b/src/OSSupport/File.h
@@ -126,7 +126,7 @@ public:
/** Returns the entire contents of the specified file as a string. Returns empty string on error. */
static AString ReadWholeFile(const AString & a_FileName);
-
+
// tolua_end
/** Returns the list of all items in the specified folder (files, folders, nix pipes, whatever's there). */
diff --git a/src/OSSupport/ListenThread.cpp b/src/OSSupport/ListenThread.cpp
deleted file mode 100644
index b029634e9..000000000
--- a/src/OSSupport/ListenThread.cpp
+++ /dev/null
@@ -1,238 +0,0 @@
-
-// ListenThread.cpp
-
-// Implements the cListenThread class representing the thread that listens for client connections
-
-#include "Globals.h"
-#include "ListenThread.h"
-
-
-
-
-
-cListenThread::cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family, const AString & a_ServiceName) :
- super(Printf("ListenThread %s", a_ServiceName.c_str())),
- m_Callback(a_Callback),
- m_Family(a_Family),
- m_ShouldReuseAddr(false),
- m_ServiceName(a_ServiceName)
-{
-}
-
-
-
-
-
-cListenThread::~cListenThread()
-{
- Stop();
-}
-
-
-
-
-
-bool cListenThread::Initialize(const AString & a_PortsString)
-{
- ASSERT(m_Sockets.empty()); // Not yet started
-
- if (!CreateSockets(a_PortsString))
- {
- return false;
- }
-
- return true;
-}
-
-
-
-
-
-bool cListenThread::Start(void)
-{
- if (m_Sockets.empty())
- {
- // There are no sockets listening, either forgotten to initialize or the user specified no listening ports
- // Report as successful, though
- return true;
- }
- return super::Start();
-}
-
-
-
-
-
-void cListenThread::Stop(void)
-{
- if (m_Sockets.empty())
- {
- // No sockets means no thread was running in the first place
- return;
- }
-
- m_ShouldTerminate = true;
-
- // Close one socket to wake the thread up from the select() call
- m_Sockets[0].CloseSocket();
-
- // Wait for the thread to finish
- super::Wait();
-
- // Close all the listening sockets:
- for (cSockets::iterator itr = m_Sockets.begin() + 1, end = m_Sockets.end(); itr != end; ++itr)
- {
- itr->CloseSocket();
- } // for itr - m_Sockets[]
- m_Sockets.clear();
-}
-
-
-
-
-
-void cListenThread::SetReuseAddr(bool a_Reuse)
-{
- ASSERT(m_Sockets.empty()); // Must not have been Initialize()d yet
-
- m_ShouldReuseAddr = a_Reuse;
-}
-
-
-
-
-
-bool cListenThread::CreateSockets(const AString & a_PortsString)
-{
- AStringVector Ports = StringSplitAndTrim(a_PortsString, ",");
-
- if (Ports.empty())
- {
- return false;
- }
-
- AString FamilyStr = m_ServiceName;
- switch (m_Family)
- {
- case cSocket::IPv4: FamilyStr.append(" IPv4"); break;
- case cSocket::IPv6: FamilyStr.append(" IPv6"); break;
- default:
- {
- ASSERT(!"Unknown address family");
- break;
- }
- }
-
- for (AStringVector::const_iterator itr = Ports.begin(), end = Ports.end(); itr != end; ++itr)
- {
- int Port = atoi(itr->c_str());
- if ((Port <= 0) || (Port > 65535))
- {
- LOGWARNING("%s: Invalid port specified: \"%s\".", FamilyStr.c_str(), itr->c_str());
- continue;
- }
- m_Sockets.push_back(cSocket::CreateSocket(m_Family));
- if (!m_Sockets.back().IsValid())
- {
- LOGWARNING("%s: Cannot create listening socket for port %d: \"%s\"", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
- m_Sockets.pop_back();
- continue;
- }
-
- if (m_ShouldReuseAddr)
- {
- if (!m_Sockets.back().SetReuseAddress())
- {
- LOG("%s: Port %d cannot reuse addr, syscall failed: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
- }
- }
-
- // Bind to port:
- bool res = false;
- switch (m_Family)
- {
- case cSocket::IPv4: res = m_Sockets.back().BindToAnyIPv4(Port); break;
- case cSocket::IPv6: res = m_Sockets.back().BindToAnyIPv6(Port); break;
- default:
- {
- ASSERT(!"Unknown address family");
- res = false;
- }
- }
- if (!res)
- {
- LOGWARNING("%s: Cannot bind port %d: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
- m_Sockets.pop_back();
- continue;
- }
-
- if (!m_Sockets.back().Listen())
- {
- LOGWARNING("%s: Cannot listen on port %d: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
- m_Sockets.pop_back();
- continue;
- }
-
- LOGINFO("%s: Port %d is open for connections", FamilyStr.c_str(), Port);
- } // for itr - Ports[]
-
- return !(m_Sockets.empty());
-}
-
-
-
-
-
-void cListenThread::Execute(void)
-{
- if (m_Sockets.empty())
- {
- LOGD("Empty cListenThread, ending thread now.");
- return;
- }
-
- // Find the highest socket number:
- cSocket::xSocket Highest = m_Sockets[0].GetSocket();
- for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr)
- {
- if (itr->GetSocket() > Highest)
- {
- Highest = itr->GetSocket();
- }
- } // for itr - m_Sockets[]
-
- while (!m_ShouldTerminate)
- {
- // Put all sockets into a FD set:
- fd_set fdRead;
- FD_ZERO(&fdRead);
- for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr)
- {
- FD_SET(itr->GetSocket(), &fdRead);
- } // for itr - m_Sockets[]
-
- timeval tv; // On Linux select() doesn't seem to wake up when socket is closed, so let's kinda busy-wait:
- tv.tv_sec = 1;
- tv.tv_usec = 0;
- if (select((int)Highest + 1, &fdRead, nullptr, nullptr, &tv) == -1)
- {
- LOG("select(R) call failed in cListenThread: \"%s\"", cSocket::GetLastErrorString().c_str());
- continue;
- }
- for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr)
- {
- if (itr->IsValid() && FD_ISSET(itr->GetSocket(), &fdRead))
- {
- cSocket Client = (m_Family == cSocket::IPv4) ? itr->AcceptIPv4() : itr->AcceptIPv6();
- if (Client.IsValid())
- {
- m_Callback.OnConnectionAccepted(Client);
- }
- }
- } // for itr - m_Sockets[]
- } // while (!m_ShouldTerminate)
-}
-
-
-
-
diff --git a/src/OSSupport/ListenThread.h b/src/OSSupport/ListenThread.h
deleted file mode 100644
index b2d806c82..000000000
--- a/src/OSSupport/ListenThread.h
+++ /dev/null
@@ -1,85 +0,0 @@
-
-// ListenThread.h
-
-// Declares the cListenThread class representing the thread that listens for client connections
-
-
-
-
-
-#pragma once
-
-#include "IsThread.h"
-#include "Socket.h"
-
-
-
-
-
-// fwd:
-class cServer;
-
-
-
-
-
-class cListenThread :
- public cIsThread
-{
- typedef cIsThread super;
-
-public:
- /** Used as the callback for connection events */
- class cCallback
- {
- public:
- virtual ~cCallback() {}
-
- /** This callback is called whenever a socket connection is accepted */
- virtual void OnConnectionAccepted(cSocket & a_Socket) = 0;
- } ;
-
- cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family, const AString & a_ServiceName = "");
- ~cListenThread();
-
- /** Creates all the sockets, returns trus if successful, false if not. */
- bool Initialize(const AString & a_PortsString);
-
- bool Start(void);
-
- void Stop(void);
-
- /** Call before Initialize() to set the "reuse" flag on the sockets */
- void SetReuseAddr(bool a_Reuse = true);
-
-protected:
- typedef std::vector<cSocket> cSockets;
-
- /** The callback which to notify of incoming connections */
- cCallback & m_Callback;
-
- /** Socket address family to use */
- cSocket::eFamily m_Family;
-
- /** Sockets that are being monitored */
- cSockets m_Sockets;
-
- /** If set to true, the SO_REUSEADDR socket option is set to true */
- bool m_ShouldReuseAddr;
-
- /** Name of the service that's listening on the ports; for logging purposes only */
- AString m_ServiceName;
-
-
- /** Fills in m_Sockets with individual sockets, each for one port specified in a_PortsString.
- Returns true if successful and at least one socket has been created
- */
- bool CreateSockets(const AString & a_PortsString);
-
- // cIsThread override:
- virtual void Execute(void) override;
-} ;
-
-
-
-
diff --git a/src/OSSupport/NetworkSingleton.cpp b/src/OSSupport/NetworkSingleton.cpp
index 92f0604cd..000b17641 100644
--- a/src/OSSupport/NetworkSingleton.cpp
+++ b/src/OSSupport/NetworkSingleton.cpp
@@ -18,7 +18,8 @@
-cNetworkSingleton::cNetworkSingleton(void)
+cNetworkSingleton::cNetworkSingleton(void):
+ m_HasTerminated(false)
{
// Windows: initialize networking:
#ifdef _WIN32
@@ -72,6 +73,29 @@ cNetworkSingleton::cNetworkSingleton(void)
cNetworkSingleton::~cNetworkSingleton()
{
+ // Check that Terminate has been called already:
+ ASSERT(m_HasTerminated);
+}
+
+
+
+
+
+cNetworkSingleton & cNetworkSingleton::Get(void)
+{
+ static cNetworkSingleton Instance;
+ return Instance;
+}
+
+
+
+
+
+void cNetworkSingleton::Terminate(void)
+{
+ ASSERT(!m_HasTerminated);
+ m_HasTerminated = true;
+
// Wait for the LibEvent event loop to terminate:
event_base_loopbreak(m_EventBase);
m_EventLoopTerminated.Wait();
@@ -96,16 +120,6 @@ cNetworkSingleton::~cNetworkSingleton()
-cNetworkSingleton & cNetworkSingleton::Get(void)
-{
- static cNetworkSingleton Instance;
- return Instance;
-}
-
-
-
-
-
void cNetworkSingleton::LogCallback(int a_Severity, const char * a_Msg)
{
switch (a_Severity)
@@ -138,6 +152,7 @@ void cNetworkSingleton::RunEventLoop(cNetworkSingleton * a_Self)
void cNetworkSingleton::AddHostnameLookup(cHostnameLookupPtr a_HostnameLookup)
{
+ ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
m_HostnameLookups.push_back(a_HostnameLookup);
}
@@ -148,6 +163,7 @@ void cNetworkSingleton::AddHostnameLookup(cHostnameLookupPtr a_HostnameLookup)
void cNetworkSingleton::RemoveHostnameLookup(const cHostnameLookup * a_HostnameLookup)
{
+ ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
for (auto itr = m_HostnameLookups.begin(), end = m_HostnameLookups.end(); itr != end; ++itr)
{
@@ -165,6 +181,7 @@ void cNetworkSingleton::RemoveHostnameLookup(const cHostnameLookup * a_HostnameL
void cNetworkSingleton::AddIPLookup(cIPLookupPtr a_IPLookup)
{
+ ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
m_IPLookups.push_back(a_IPLookup);
}
@@ -175,6 +192,7 @@ void cNetworkSingleton::AddIPLookup(cIPLookupPtr a_IPLookup)
void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup)
{
+ ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
for (auto itr = m_IPLookups.begin(), end = m_IPLookups.end(); itr != end; ++itr)
{
@@ -192,6 +210,7 @@ void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup)
void cNetworkSingleton::AddLink(cTCPLinkImplPtr a_Link)
{
+ ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
m_Connections.push_back(a_Link);
}
@@ -202,6 +221,7 @@ void cNetworkSingleton::AddLink(cTCPLinkImplPtr a_Link)
void cNetworkSingleton::RemoveLink(const cTCPLinkImpl * a_Link)
{
+ ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr)
{
@@ -219,6 +239,7 @@ void cNetworkSingleton::RemoveLink(const cTCPLinkImpl * a_Link)
void cNetworkSingleton::AddServer(cServerHandleImplPtr a_Server)
{
+ ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
m_Servers.push_back(a_Server);
}
@@ -229,6 +250,7 @@ void cNetworkSingleton::AddServer(cServerHandleImplPtr a_Server)
void cNetworkSingleton::RemoveServer(const cServerHandleImpl * a_Server)
{
+ ASSERT(!m_HasTerminated);
cCSLock Lock(m_CS);
for (auto itr = m_Servers.begin(), end = m_Servers.end(); itr != end; ++itr)
{
diff --git a/src/OSSupport/NetworkSingleton.h b/src/OSSupport/NetworkSingleton.h
index 1d26fc8f4..e27e19012 100644
--- a/src/OSSupport/NetworkSingleton.h
+++ b/src/OSSupport/NetworkSingleton.h
@@ -4,7 +4,8 @@
// Declares the cNetworkSingleton class representing the storage for global data pertaining to network API
// such as a list of all connections, all listening sockets and the LibEvent dispatch thread.
-// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead
+// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead;
+// the only exception being the main app entrypoint that needs to call Terminate before quitting.
@@ -48,6 +49,11 @@ public:
/** Returns the singleton instance of this class */
static cNetworkSingleton & Get(void);
+ /** Terminates all network-related threads.
+ To be used only on app shutdown.
+ MSVC runtime requires that the LibEvent networking be shut down before the main() function is exitted; this is the way to do it. */
+ void Terminate(void);
+
/** Returns the main LibEvent handle for event registering. */
event_base * GetEventBase(void) { return m_EventBase; }
@@ -113,6 +119,9 @@ protected:
/** Event that gets signalled when the event loop terminates. */
cEvent m_EventLoopTerminated;
+ /** Set to true if Terminate has been called. */
+ volatile bool m_HasTerminated;
+
/** Initializes the LibEvent internals. */
cNetworkSingleton(void);
diff --git a/src/OSSupport/ServerHandleImpl.cpp b/src/OSSupport/ServerHandleImpl.cpp
index ba38dbf2e..5fc5662e1 100644
--- a/src/OSSupport/ServerHandleImpl.cpp
+++ b/src/OSSupport/ServerHandleImpl.cpp
@@ -83,6 +83,9 @@ void cServerHandleImpl::Close(void)
// Remove the ptr to self, so that the object may be freed:
m_SelfPtr.reset();
+
+ // Remove self from cNetworkSingleton:
+ cNetworkSingleton::Get().RemoveServer(this);
}
@@ -157,10 +160,6 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
int res = setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero));
err = EVUTIL_SOCKET_ERROR();
NeedsTwoSockets = ((res == SOCKET_ERROR) && (err == WSAENOPROTOOPT));
- LOGD("setsockopt(IPV6_V6ONLY) returned %d, err is %d (%s). %s",
- res, err, evutil_socket_error_to_string(err),
- NeedsTwoSockets ? "Second socket will be created" : "Second socket not needed"
- );
#else
setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero));
#endif
@@ -256,19 +255,20 @@ void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_
// Get the textual IP address and port number out of a_Addr:
char IPAddress[128];
- evutil_inet_ntop(a_Addr->sa_family, a_Addr->sa_data, IPAddress, ARRAYCOUNT(IPAddress));
UInt16 Port = 0;
switch (a_Addr->sa_family)
{
case AF_INET:
{
sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(a_Addr);
+ evutil_inet_ntop(AF_INET, sin, IPAddress, ARRAYCOUNT(IPAddress));
Port = ntohs(sin->sin_port);
break;
}
case AF_INET6:
{
sockaddr_in6 * sin6 = reinterpret_cast<sockaddr_in6 *>(a_Addr);
+ evutil_inet_ntop(AF_INET, sin6, IPAddress, ARRAYCOUNT(IPAddress));
Port = ntohs(sin6->sin6_port);
break;
}
diff --git a/src/OSSupport/SocketThreads.cpp b/src/OSSupport/SocketThreads.cpp
deleted file mode 100644
index 153d6ed1d..000000000
--- a/src/OSSupport/SocketThreads.cpp
+++ /dev/null
@@ -1,702 +0,0 @@
-
-// cSocketThreads.cpp
-
-// Implements the cSocketThreads class representing the heart of MCS's client networking.
-// This object takes care of network communication, groups sockets into threads and uses as little threads as possible for full read / write support
-// For more detail, see http://forum.mc-server.org/showthread.php?tid=327
-
-#include "Globals.h"
-#include "SocketThreads.h"
-#include "Errors.h"
-
-
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-// cSocketThreads:
-
-cSocketThreads::cSocketThreads(void)
-{
-}
-
-
-
-
-
-cSocketThreads::~cSocketThreads()
-{
- for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
- {
- delete *itr;
- } // for itr - m_Threads[]
- m_Threads.clear();
-}
-
-
-
-
-
-
-bool cSocketThreads::AddClient(const cSocket & a_Socket, cCallback * a_Client)
-{
- // Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client
-
- // Try to add to existing threads:
- cCSLock Lock(m_CS);
- for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
- {
- if ((*itr)->IsValid() && (*itr)->HasEmptySlot())
- {
- (*itr)->AddClient(a_Socket, a_Client);
- return true;
- }
- }
-
- // No thread has free space, create a new one:
- LOGD("Creating a new cSocketThread (currently have " SIZE_T_FMT ")", m_Threads.size());
- cSocketThread * Thread = new cSocketThread(this);
- if (!Thread->Start())
- {
- // There was an error launching the thread (but it was already logged along with the reason)
- LOGERROR("A new cSocketThread failed to start");
- delete Thread;
- Thread = nullptr;
- return false;
- }
- Thread->AddClient(a_Socket, a_Client);
- m_Threads.push_back(Thread);
- return true;
-}
-
-
-
-
-
-void cSocketThreads::RemoveClient(const cCallback * a_Client)
-{
- // Remove the associated socket and the client from processing
-
- cCSLock Lock(m_CS);
- for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
- {
- if ((*itr)->RemoveClient(a_Client))
- {
- return;
- }
- } // for itr - m_Threads[]
-
- // This client wasn't found.
- // It's not an error, because it may have been removed by a different thread in the meantime.
-}
-
-
-
-
-
-void cSocketThreads::NotifyWrite(const cCallback * a_Client)
-{
- // Notifies the thread responsible for a_Client that the client has something to write
-
- cCSLock Lock(m_CS);
- for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
- {
- if ((*itr)->NotifyWrite(a_Client))
- {
- return;
- }
- } // for itr - m_Threads[]
-
- // Cannot assert - this normally happens if a client disconnects and has pending packets, the cServer::cNotifyWriteThread will call this on invalid clients too
- // ASSERT(!"Notifying write to an unknown client");
-}
-
-
-
-
-
-void cSocketThreads::Write(const cCallback * a_Client, const AString & a_Data)
-{
- // Puts a_Data into outgoing data queue for a_Client
- cCSLock Lock(m_CS);
- for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
- {
- if ((*itr)->Write(a_Client, a_Data))
- {
- return;
- }
- } // for itr - m_Threads[]
-
- // This may be perfectly legal, if the socket has been destroyed and the client is finishing up
- // ASSERT(!"Writing to an unknown socket");
-}
-
-
-
-
-
-////////////////////////////////////////////////////////////////////////////////
-// cSocketThreads::cSocketThread:
-
-cSocketThreads::cSocketThread::cSocketThread(cSocketThreads * a_Parent) :
- cIsThread("cSocketThread"),
- m_Parent(a_Parent),
- m_NumSlots(0)
-{
- // Nothing needed yet
-}
-
-
-
-
-
-cSocketThreads::cSocketThread::~cSocketThread()
-{
- m_ShouldTerminate = true;
-
- // Notify the thread:
- ASSERT(m_ControlSocket2.IsValid());
- m_ControlSocket2.Send("a", 1);
-
- // Wait for the thread to finish:
- Wait();
-
- // Close the control sockets:
- m_ControlSocket1.CloseSocket();
- m_ControlSocket2.CloseSocket();
-}
-
-
-
-
-
-void cSocketThreads::cSocketThread::AddClient(const cSocket & a_Socket, cCallback * a_Client)
-{
- ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
- ASSERT(m_NumSlots < MAX_SLOTS); // Use HasEmptySlot() to check before adding
-
- m_Slots[m_NumSlots].m_Client = a_Client;
- m_Slots[m_NumSlots].m_Socket = a_Socket;
- m_Slots[m_NumSlots].m_Socket.SetNonBlocking();
- m_Slots[m_NumSlots].m_Outgoing.clear();
- m_Slots[m_NumSlots].m_State = sSlot::ssNormal;
- m_NumSlots++;
-
- // Notify the thread of the change:
- ASSERT(m_ControlSocket2.IsValid());
- m_ControlSocket2.Send("a", 1);
-}
-
-
-
-
-
-bool cSocketThreads::cSocketThread::RemoveClient(const cCallback * a_Client)
-{
- ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
-
- if (m_NumSlots == 0)
- {
- return false;
- }
-
- for (int i = m_NumSlots - 1; i >= 0 ; --i)
- {
- if (m_Slots[i].m_Client != a_Client)
- {
- continue;
- }
-
- // Found the slot:
- if (m_Slots[i].m_State == sSlot::ssRemoteClosed)
- {
- // The remote has already closed the socket, remove the slot altogether:
- if (m_Slots[i].m_Socket.IsValid())
- {
- m_Slots[i].m_Socket.CloseSocket();
- }
- m_Slots[i] = m_Slots[--m_NumSlots];
- }
- else
- {
- // Query and queue the last batch of outgoing data:
- AString Data;
- m_Slots[i].m_Client->GetOutgoingData(Data);
- m_Slots[i].m_Outgoing.append(Data);
- if (m_Slots[i].m_Outgoing.empty())
- {
- // No more outgoing data, shut the socket down immediately:
- m_Slots[i].m_Socket.ShutdownReadWrite();
- m_Slots[i].m_State = sSlot::ssShuttingDown;
- }
- else
- {
- // More data to send, shut down reading and wait for the rest to get sent:
- m_Slots[i].m_State = sSlot::ssWritingRestOut;
- }
- m_Slots[i].m_Client = nullptr;
- }
-
- // Notify the thread of the change:
- ASSERT(m_ControlSocket2.IsValid());
- m_ControlSocket2.Send("r", 1);
- return true;
- } // for i - m_Slots[]
-
- // Not found
- return false;
-}
-
-
-
-
-
-bool cSocketThreads::cSocketThread::HasClient(const cCallback * a_Client) const
-{
- ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
-
- for (int i = m_NumSlots - 1; i >= 0; --i)
- {
- if (m_Slots[i].m_Client == a_Client)
- {
- return true;
- }
- } // for i - m_Slots[]
- return false;
-}
-
-
-
-
-
-bool cSocketThreads::cSocketThread::HasSocket(const cSocket * a_Socket) const
-{
- for (int i = m_NumSlots - 1; i >= 0; --i)
- {
- if (m_Slots[i].m_Socket == *a_Socket)
- {
- return true;
- }
- } // for i - m_Slots[]
- return false;
-}
-
-
-
-
-
-bool cSocketThreads::cSocketThread::NotifyWrite(const cCallback * a_Client)
-{
- ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
-
- if (HasClient(a_Client))
- {
- // Notify the thread that there's another packet in the queue:
- ASSERT(m_ControlSocket2.IsValid());
- m_ControlSocket2.Send("q", 1);
- return true;
- }
- return false;
-}
-
-
-
-
-
-bool cSocketThreads::cSocketThread::Write(const cCallback * a_Client, const AString & a_Data)
-{
- ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
- for (int i = m_NumSlots - 1; i >= 0; --i)
- {
- if (m_Slots[i].m_Client == a_Client)
- {
- m_Slots[i].m_Outgoing.append(a_Data);
-
- // Notify the thread that there's data in the queue:
- ASSERT(m_ControlSocket2.IsValid());
- m_ControlSocket2.Send("q", 1);
-
- return true;
- }
- } // for i - m_Slots[]
- return false;
-}
-
-
-
-
-
-bool cSocketThreads::cSocketThread::Start(void)
-{
- // Create the control socket listener
- m_ControlSocket2 = cSocket::CreateSocket(cSocket::IPv4);
- if (!m_ControlSocket2.IsValid())
- {
- LOGERROR("Cannot create a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
- return false;
- }
- if (!m_ControlSocket2.BindToLocalhostIPv4(cSocket::ANY_PORT))
- {
- LOGERROR("Cannot bind a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
- m_ControlSocket2.CloseSocket();
- return false;
- }
- if (!m_ControlSocket2.Listen(1))
- {
- LOGERROR("Cannot listen on a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
- m_ControlSocket2.CloseSocket();
- return false;
- }
- if (m_ControlSocket2.GetPort() == 0)
- {
- LOGERROR("Cannot determine Control socket port (\"%s\"); conitnuing, but the server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
- m_ControlSocket2.CloseSocket();
- return false;
- }
-
- // Start the thread
- if (!super::Start())
- {
- LOGERROR("Cannot start new cSocketThread");
- m_ControlSocket2.CloseSocket();
- return false;
- }
-
- // Finish connecting the control socket by accepting connection from the thread's socket
- cSocket tmp = m_ControlSocket2.AcceptIPv4();
- if (!tmp.IsValid())
- {
- LOGERROR("Cannot link Control sockets for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
- m_ControlSocket2.CloseSocket();
- return false;
- }
- m_ControlSocket2.CloseSocket();
- m_ControlSocket2 = tmp;
-
- return true;
-}
-
-
-
-
-
-void cSocketThreads::cSocketThread::Execute(void)
-{
- // Connect the "client" part of the Control socket:
- m_ControlSocket1 = cSocket::CreateSocket(cSocket::IPv4);
- ASSERT(m_ControlSocket2.GetPort() != 0); // We checked in the Start() method, but let's be sure
- if (!m_ControlSocket1.ConnectToLocalhostIPv4(m_ControlSocket2.GetPort()))
- {
- LOGERROR("Cannot connect Control sockets for a cSocketThread (\"%s\"); continuing, but the server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
- m_ControlSocket2.CloseSocket();
- return;
- }
-
- // The main thread loop:
- while (!m_ShouldTerminate)
- {
- // Read outgoing data from the clients:
- QueueOutgoingData();
-
- // Put sockets into the sets
- fd_set fdRead;
- fd_set fdWrite;
- cSocket::xSocket Highest = m_ControlSocket1.GetSocket();
- PrepareSets(&fdRead, &fdWrite, Highest);
-
- // Wait for the sockets:
- timeval Timeout;
- Timeout.tv_sec = 5;
- Timeout.tv_usec = 0;
- if (select((int)Highest + 1, &fdRead, &fdWrite, nullptr, &Timeout) == -1)
- {
- LOG("select() call failed in cSocketThread: \"%s\"", cSocket::GetLastErrorString().c_str());
- continue;
- }
-
- // Perform the IO:
- ReadFromSockets(&fdRead);
- WriteToSockets(&fdWrite);
- CleanUpShutSockets();
- } // while (!mShouldTerminate)
-}
-
-
-
-
-
-void cSocketThreads::cSocketThread::PrepareSets(fd_set * a_Read, fd_set * a_Write, cSocket::xSocket & a_Highest)
-{
- FD_ZERO(a_Read);
- FD_ZERO(a_Write);
- FD_SET(m_ControlSocket1.GetSocket(), a_Read);
-
- cCSLock Lock(m_Parent->m_CS);
- for (int i = m_NumSlots - 1; i >= 0; --i)
- {
- if (!m_Slots[i].m_Socket.IsValid())
- {
- continue;
- }
- if (m_Slots[i].m_State == sSlot::ssRemoteClosed)
- {
- // This socket won't provide nor consume any data anymore, don't put it in the Set
- continue;
- }
- cSocket::xSocket s = m_Slots[i].m_Socket.GetSocket();
- FD_SET(s, a_Read);
- if (s > a_Highest)
- {
- a_Highest = s;
- }
- if (!m_Slots[i].m_Outgoing.empty())
- {
- // There's outgoing data for the socket, put it in the Write set
- FD_SET(s, a_Write);
- }
- } // for i - m_Slots[]
-}
-
-
-
-
-
-void cSocketThreads::cSocketThread::ReadFromSockets(fd_set * a_Read)
-{
- // Read on available sockets:
-
- // Reset Control socket state:
- if (FD_ISSET(m_ControlSocket1.GetSocket(), a_Read))
- {
- char Dummy[128];
- m_ControlSocket1.Receive(Dummy, sizeof(Dummy), 0);
- }
-
- // Read from clients:
- cCSLock Lock(m_Parent->m_CS);
- for (int i = m_NumSlots - 1; i >= 0; --i)
- {
- cSocket::xSocket Socket = m_Slots[i].m_Socket.GetSocket();
- if (!cSocket::IsValidSocket(Socket) || !FD_ISSET(Socket, a_Read))
- {
- continue;
- }
- char Buffer[1024];
- int Received = m_Slots[i].m_Socket.Receive(Buffer, ARRAYCOUNT(Buffer), 0);
- if (Received <= 0)
- {
- if (cSocket::GetLastError() != cSocket::ErrWouldBlock)
- {
- // The socket has been closed by the remote party
- switch (m_Slots[i].m_State)
- {
- case sSlot::ssNormal:
- {
- // Close the socket on our side:
- m_Slots[i].m_State = sSlot::ssRemoteClosed;
- m_Slots[i].m_Socket.CloseSocket();
-
- // Notify the callback that the remote has closed the socket, *after* removing the socket:
- cCallback * client = m_Slots[i].m_Client;
- m_Slots[i] = m_Slots[--m_NumSlots];
- if (client != nullptr)
- {
- client->SocketClosed();
- }
- break;
- }
- case sSlot::ssWritingRestOut:
- case sSlot::ssShuttingDown:
- case sSlot::ssShuttingDown2:
- {
- // Force-close the socket and remove the slot:
- m_Slots[i].m_Socket.CloseSocket();
- m_Slots[i] = m_Slots[--m_NumSlots];
- break;
- }
- default:
- {
- LOG("%s: Unexpected socket state: %d (%s)",
- __FUNCTION__, m_Slots[i].m_Socket.GetSocket(), m_Slots[i].m_Socket.GetIPString().c_str()
- );
- ASSERT(!"Unexpected socket state");
- break;
- }
- } // switch (m_Slots[i].m_State)
- }
- }
- else
- {
- if (m_Slots[i].m_Client != nullptr)
- {
- m_Slots[i].m_Client->DataReceived(Buffer, Received);
- }
- }
- } // for i - m_Slots[]
-}
-
-
-
-
-
-void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write)
-{
- // Write to available client sockets:
- cCSLock Lock(m_Parent->m_CS);
- for (int i = m_NumSlots - 1; i >= 0; --i)
- {
- cSocket::xSocket Socket = m_Slots[i].m_Socket.GetSocket();
- if (!cSocket::IsValidSocket(Socket) || !FD_ISSET(Socket, a_Write))
- {
- continue;
- }
- if (m_Slots[i].m_Outgoing.empty())
- {
- // Request another chunk of outgoing data:
- if (m_Slots[i].m_Client != nullptr)
- {
- AString Data;
- m_Slots[i].m_Client->GetOutgoingData(Data);
- m_Slots[i].m_Outgoing.append(Data);
- }
- if (m_Slots[i].m_Outgoing.empty())
- {
- // No outgoing data is ready
- if (m_Slots[i].m_State == sSlot::ssWritingRestOut)
- {
- m_Slots[i].m_State = sSlot::ssShuttingDown;
- m_Slots[i].m_Socket.ShutdownReadWrite();
- }
- continue;
- }
- } // if (outgoing data is empty)
-
- if (m_Slots[i].m_State == sSlot::ssRemoteClosed)
- {
- continue;
- }
-
- if (!SendDataThroughSocket(m_Slots[i].m_Socket, m_Slots[i].m_Outgoing))
- {
- int Err = cSocket::GetLastError();
- LOGWARNING("Error %d while writing to client \"%s\", disconnecting. \"%s\"", Err, m_Slots[i].m_Socket.GetIPString().c_str(), GetOSErrorString(Err).c_str());
- m_Slots[i].m_Socket.CloseSocket();
- if (m_Slots[i].m_Client != nullptr)
- {
- m_Slots[i].m_Client->SocketClosed();
- }
- continue;
- }
-
- if (m_Slots[i].m_Outgoing.empty() && (m_Slots[i].m_State == sSlot::ssWritingRestOut))
- {
- m_Slots[i].m_State = sSlot::ssShuttingDown;
- m_Slots[i].m_Socket.ShutdownReadWrite();
- }
-
- // _X: If there's data left, it means the client is not reading fast enough, the server would unnecessarily spin in the main loop with zero actions taken; so signalling is disabled
- // This means that if there's data left, it will be sent only when there's incoming data or someone queues another packet (for any socket handled by this thread)
- /*
- // If there's any data left, signalize the Control socket:
- if (!m_Slots[i].m_Outgoing.empty())
- {
- ASSERT(m_ControlSocket2.IsValid());
- m_ControlSocket2.Send("q", 1);
- }
- */
- } // for i - m_Slots[i]
-}
-
-
-
-
-
-bool cSocketThreads::cSocketThread::SendDataThroughSocket(cSocket & a_Socket, AString & a_Data)
-{
- // Send data in smaller chunks, so that the OS send buffers aren't overflown easily
- while (!a_Data.empty())
- {
- size_t NumToSend = std::min(a_Data.size(), (size_t)1024);
- int Sent = a_Socket.Send(a_Data.data(), NumToSend);
- if (Sent < 0)
- {
- int Err = cSocket::GetLastError();
- if (Err == cSocket::ErrWouldBlock)
- {
- // The OS send buffer is full, leave the outgoing data for the next time
- return true;
- }
- // An error has occured
- return false;
- }
- if (Sent == 0)
- {
- a_Socket.CloseSocket();
- return true;
- }
- a_Data.erase(0, Sent);
- }
- return true;
-}
-
-
-
-
-
-void cSocketThreads::cSocketThread::CleanUpShutSockets(void)
-{
- cCSLock Lock(m_Parent->m_CS);
- for (int i = m_NumSlots - 1; i >= 0; i--)
- {
- switch (m_Slots[i].m_State)
- {
- case sSlot::ssShuttingDown2:
- {
- // The socket has reached the shutdown timeout, close it and clear its slot:
- m_Slots[i].m_Socket.CloseSocket();
- m_Slots[i] = m_Slots[--m_NumSlots];
- break;
- }
- case sSlot::ssShuttingDown:
- {
- // The socket has been shut down for a single thread loop, let it loop once more before closing:
- m_Slots[i].m_State = sSlot::ssShuttingDown2;
- break;
- }
- default: break;
- }
- } // for i - m_Slots[]
-}
-
-
-
-
-void cSocketThreads::cSocketThread::QueueOutgoingData(void)
-{
- cCSLock Lock(m_Parent->m_CS);
- for (int i = 0; i < m_NumSlots; i++)
- {
- if (m_Slots[i].m_Client != nullptr)
- {
- AString Data;
- m_Slots[i].m_Client->GetOutgoingData(Data);
- m_Slots[i].m_Outgoing.append(Data);
- }
- if (m_Slots[i].m_Outgoing.empty())
- {
- // No outgoing data is ready
- if (m_Slots[i].m_State == sSlot::ssWritingRestOut)
- {
- // The socket doesn't want to be kept alive anymore, and doesn't have any remaining data to send.
- // Shut it down and then close it after a timeout, or when the other side agrees
- m_Slots[i].m_State = sSlot::ssShuttingDown;
- m_Slots[i].m_Socket.ShutdownReadWrite();
- }
- continue;
- }
- }
-}
-
-
-
-
diff --git a/src/OSSupport/SocketThreads.h b/src/OSSupport/SocketThreads.h
deleted file mode 100644
index df819468d..000000000
--- a/src/OSSupport/SocketThreads.h
+++ /dev/null
@@ -1,194 +0,0 @@
-
-// SocketThreads.h
-
-// Interfaces to the cSocketThreads class representing the heart of MCS's client networking.
-// This object takes care of network communication, groups sockets into threads and uses as little threads as possible for full read / write support
-// For more detail, see http://forum.mc-server.org/showthread.php?tid=327
-
-/*
-Additional details:
-When a client wants to terminate the connection, they call the RemoveClient() function. This calls the
-callback one last time to read all the available outgoing data, putting it in the slot's m_OutgoingData
-buffer. Then it marks the slot as having no callback. The socket is kept alive until its outgoing data
-queue is empty, then shutdown is called on it and finally the socket is closed after a timeout.
-If at any time within this the remote end closes the socket, then the socket is closed directly.
-As soon as the socket is closed, the slot is finally removed from the SocketThread.
-The graph in $/docs/SocketThreads States.gv shows the state-machine transitions of the slot.
-*/
-
-
-
-
-
-/** How many clients should one thread handle? (must be less than FD_SETSIZE for your platform) */
-#define MAX_SLOTS 63
-
-
-
-
-
-#pragma once
-
-#include "Socket.h"
-#include "IsThread.h"
-
-
-
-
-// Check MAX_SLOTS:
-#if MAX_SLOTS >= FD_SETSIZE
- #error "MAX_SLOTS must be less than FD_SETSIZE for your platform! (otherwise select() won't work)"
-#endif
-
-
-
-
-
-// fwd:
-class cSocket;
-class cClientHandle;
-
-
-
-
-
-class cSocketThreads
-{
-public:
-
- // Clients of cSocketThreads must implement this interface to be able to communicate
- class cCallback
- {
- public:
- // Force a virtual destructor in all subclasses:
- virtual ~cCallback() {}
-
- /** Called when data is received from the remote party.
- SocketThreads does not care about the return value, others can use it for their specific purpose -
- for example HTTPServer uses it to signal if the connection was terminated as a result of the data received. */
- virtual bool DataReceived(const char * a_Data, size_t a_Size) = 0;
-
- /** Called when data can be sent to remote party
- The function is supposed to *set* outgoing data to a_Data (overwrite) */
- virtual void GetOutgoingData(AString & a_Data) = 0;
-
- /** Called when the socket has been closed for any reason */
- virtual void SocketClosed(void) = 0;
- } ;
-
-
- cSocketThreads(void);
- ~cSocketThreads();
-
- /** Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client; returns true if successful */
- bool AddClient(const cSocket & a_Socket, cCallback * a_Client);
-
- /** Remove the associated socket and the client from processing.
- The socket is left to send its last outgoing data and is removed only after all its m_Outgoing is sent
- and after the socket is properly shutdown (unless the remote disconnects before that)
- */
- void RemoveClient(const cCallback * a_Client);
-
- /** Notify the thread responsible for a_Client that the client has something to write */
- void NotifyWrite(const cCallback * a_Client);
-
- /** Puts a_Data into outgoing data queue for a_Client */
- void Write(const cCallback * a_Client, const AString & a_Data);
-
-private:
-
- class cSocketThread :
- public cIsThread
- {
- typedef cIsThread super;
-
- public:
-
- cSocketThread(cSocketThreads * a_Parent);
- virtual ~cSocketThread();
-
- // All these methods assume parent's m_CS is locked
- bool HasEmptySlot(void) const {return m_NumSlots < MAX_SLOTS; }
- bool IsEmpty (void) const {return m_NumSlots == 0; }
-
- void AddClient (const cSocket & a_Socket, cCallback * a_Client); // Takes ownership of the socket
- bool RemoveClient(const cCallback * a_Client); // Returns true if removed, false if not found
- bool HasClient (const cCallback * a_Client) const;
- bool HasSocket (const cSocket * a_Socket) const;
- bool NotifyWrite (const cCallback * a_Client); // Returns true if client handled by this thread
- bool Write (const cCallback * a_Client, const AString & a_Data); // Returns true if client handled by this thread
-
- bool Start(void); // Hide the cIsThread's Start method, we need to provide our own startup to create the control socket
-
- bool IsValid(void) const {return m_ControlSocket2.IsValid(); } // If the Control socket dies, the thread is not valid anymore
-
- private:
-
- cSocketThreads * m_Parent;
-
- // Two ends of the control socket, the first is select()-ed, the second is written to for notifications
- cSocket m_ControlSocket1;
- cSocket m_ControlSocket2;
-
- // Socket-client-dataqueues-state quadruplets.
- // Manipulation with these assumes that the parent's m_CS is locked
- struct sSlot
- {
- /** The socket is primarily owned by this object */
- cSocket m_Socket;
-
- /** The callback to call for events. May be nullptr */
- cCallback * m_Client;
-
- /** If sending writes only partial data, the rest is stored here for another send.
- Also used when the slot is being removed to store the last batch of outgoing data. */
- AString m_Outgoing;
-
- enum eState
- {
- ssNormal, ///< Normal read / write operations
- ssWritingRestOut, ///< The client callback was removed, continue to send outgoing data
- ssShuttingDown, ///< The last outgoing data has been sent, the socket has called shutdown()
- ssShuttingDown2, ///< The shutdown has been done at least 1 thread loop ago (timeout detection)
- ssRemoteClosed, ///< The remote end has closed the connection (and we still have a client callback)
- } m_State;
- } ;
-
- sSlot m_Slots[MAX_SLOTS];
- int m_NumSlots; // Number of slots actually used
-
- virtual void Execute(void) override;
-
- /** Prepares the Read and Write socket sets for select()
- Puts all sockets into the read set, along with m_ControlSocket1.
- Only sockets that have outgoing data queued on them are put in the write set.*/
- void PrepareSets(fd_set * a_ReadSet, fd_set * a_WriteSet, cSocket::xSocket & a_Highest);
-
- /** Reads from sockets indicated in a_Read */
- void ReadFromSockets(fd_set * a_Read);
-
- /** Writes to sockets indicated in a_Write */
- void WriteToSockets (fd_set * a_Write);
-
- /** Sends data through the specified socket, trying to fill the OS send buffer in chunks.
- Returns true if there was no error while sending, false if an error has occured.
- Modifies a_Data to contain only the unsent data. */
- bool SendDataThroughSocket(cSocket & a_Socket, AString & a_Data);
-
- /** Removes those slots in ssShuttingDown2 state, sets those with ssShuttingDown state to ssShuttingDown2 */
- void CleanUpShutSockets(void);
-
- /** Calls each client's callback to retrieve outgoing data for that client. */
- void QueueOutgoingData(void);
- } ;
-
- typedef std::list<cSocketThread *> cSocketThreadList;
-
-
- cCriticalSection m_CS;
- cSocketThreadList m_Threads;
-} ;
-
-
-
-
diff --git a/src/OSSupport/TCPLinkImpl.cpp b/src/OSSupport/TCPLinkImpl.cpp
index b4cefa60c..f97db7582 100644
--- a/src/OSSupport/TCPLinkImpl.cpp
+++ b/src/OSSupport/TCPLinkImpl.cpp
@@ -17,8 +17,9 @@
cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks):
super(a_LinkCallbacks),
- m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE))
+ m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE))
{
+ LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent);
}
@@ -27,9 +28,11 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks):
cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImplPtr a_Server, const sockaddr * a_Address, socklen_t a_AddrLen):
super(a_LinkCallbacks),
- m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE)),
+ m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE)),
m_Server(a_Server)
{
+ LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent);
+
// Update the endpoint addresses:
UpdateLocalAddress();
UpdateAddress(a_Address, a_AddrLen, m_RemoteIP, m_RemotePort);
@@ -41,6 +44,7 @@ cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_L
cTCPLinkImpl::~cTCPLinkImpl()
{
+ LOGD("Deleting cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent);
bufferevent_free(m_BufferEvent);
}
@@ -179,6 +183,8 @@ void cTCPLinkImpl::ReadCallback(bufferevent * a_BufferEvent, void * a_Self)
void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self)
{
+ LOGD("cTCPLink event callback for link %p, BEV %p; what = 0x%02x", a_Self, a_BufferEvent, a_What);
+
ASSERT(a_Self != nullptr);
cTCPLinkImplPtr Self = static_cast<cTCPLinkImpl *>(a_Self)->m_Self;
diff --git a/src/PolarSSL++/BlockingSslClientSocket.cpp b/src/PolarSSL++/BlockingSslClientSocket.cpp
index 59e1281ac..821125b31 100644
--- a/src/PolarSSL++/BlockingSslClientSocket.cpp
+++ b/src/PolarSSL++/BlockingSslClientSocket.cpp
@@ -10,6 +10,80 @@
+////////////////////////////////////////////////////////////////////////////////
+// cBlockingSslClientSocketConnectCallbacks:
+
+class cBlockingSslClientSocketConnectCallbacks:
+ public cNetwork::cConnectCallbacks
+{
+ /** The socket object that is using this instance of the callbacks. */
+ cBlockingSslClientSocket & m_Socket;
+
+ virtual void OnConnected(cTCPLink & a_Link) override
+ {
+ m_Socket.OnConnected();
+ }
+
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
+ {
+ m_Socket.OnConnectError(a_ErrorMsg);
+ }
+
+public:
+ cBlockingSslClientSocketConnectCallbacks(cBlockingSslClientSocket & a_Socket):
+ m_Socket(a_Socket)
+ {
+ }
+};
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cBlockingSslClientSocketLinkCallbacks:
+
+class cBlockingSslClientSocketLinkCallbacks:
+ public cTCPLink::cCallbacks
+{
+ cBlockingSslClientSocket & m_Socket;
+
+ virtual void OnLinkCreated(cTCPLinkPtr a_Link) override
+ {
+ m_Socket.SetLink(a_Link);
+ }
+
+
+ virtual void OnReceivedData(const char * a_Data, size_t a_Length)
+ {
+ m_Socket.OnReceivedData(a_Data, a_Length);
+ }
+
+
+ virtual void OnRemoteClosed(void)
+ {
+ m_Socket.OnDisconnected();
+ }
+
+
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg)
+ {
+ m_Socket.OnDisconnected();
+ }
+public:
+ cBlockingSslClientSocketLinkCallbacks(cBlockingSslClientSocket & a_Socket):
+ m_Socket(a_Socket)
+ {
+ }
+};
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cBlockingSslClientSocket:
+
cBlockingSslClientSocket::cBlockingSslClientSocket(void) :
m_Ssl(*this),
m_IsConnected(false)
@@ -32,10 +106,19 @@ bool cBlockingSslClientSocket::Connect(const AString & a_ServerName, UInt16 a_Po
}
// Connect the underlying socket:
- m_Socket.CreateSocket(cSocket::IPv4);
- if (!m_Socket.ConnectIPv4(a_ServerName.c_str(), a_Port))
+ m_ServerName = a_ServerName;
+ if (!cNetwork::Connect(a_ServerName, a_Port,
+ std::make_shared<cBlockingSslClientSocketConnectCallbacks>(*this),
+ std::make_shared<cBlockingSslClientSocketLinkCallbacks>(*this))
+ )
+ {
+ return false;
+ }
+
+ // Wait for the connection to succeed or fail:
+ m_Event.Wait();
+ if (!m_IsConnected)
{
- Printf(m_LastErrorText, "Socket connect failed: %s", m_Socket.GetLastErrorString().c_str());
return false;
}
@@ -102,7 +185,7 @@ bool cBlockingSslClientSocket::Send(const void * a_Data, size_t a_NumBytes)
ASSERT(m_IsConnected);
// Keep sending the data until all of it is sent:
- const char * Data = (const char *)a_Data;
+ const char * Data = reinterpret_cast<const char *>(a_Data);
size_t NumBytes = a_NumBytes;
for (;;)
{
@@ -156,7 +239,8 @@ void cBlockingSslClientSocket::Disconnect(void)
}
m_Ssl.NotifyClose();
- m_Socket.CloseSocket();
+ m_Socket->Close();
+ m_Socket.reset();
m_IsConnected = false;
}
@@ -166,13 +250,25 @@ void cBlockingSslClientSocket::Disconnect(void)
int cBlockingSslClientSocket::ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes)
{
- int res = m_Socket.Receive((char *)a_Buffer, a_NumBytes, 0);
- if (res < 0)
+ // Wait for any incoming data, if there is none:
+ cCSLock Lock(m_CSIncomingData);
+ while (m_IsConnected && m_IncomingData.empty())
+ {
+ cCSUnlock Unlock(Lock);
+ m_Event.Wait();
+ }
+
+ // If we got disconnected, report an error after processing all data:
+ if (!m_IsConnected && m_IncomingData.empty())
{
- // PolarSSL's net routines distinguish between connection reset and general failure, we don't need to
return POLARSSL_ERR_NET_RECV_FAILED;
}
- return res;
+
+ // Copy the data from the incoming buffer into the specified space:
+ size_t NumToCopy = std::min(a_NumBytes, m_IncomingData.size());
+ memcpy(a_Buffer, m_IncomingData.data(), NumToCopy);
+ m_IncomingData.erase(0, NumToCopy);
+ return static_cast<int>(NumToCopy);
}
@@ -181,13 +277,69 @@ int cBlockingSslClientSocket::ReceiveEncrypted(unsigned char * a_Buffer, size_t
int cBlockingSslClientSocket::SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes)
{
- int res = m_Socket.Send((const char *)a_Buffer, a_NumBytes);
- if (res < 0)
+ cTCPLinkPtr Socket(m_Socket); // Make a copy so that multiple threads don't race on deleting the socket.
+ if (Socket == nullptr)
+ {
+ return POLARSSL_ERR_NET_SEND_FAILED;
+ }
+ if (!Socket->Send(a_Buffer, a_NumBytes))
{
// PolarSSL's net routines distinguish between connection reset and general failure, we don't need to
return POLARSSL_ERR_NET_SEND_FAILED;
}
- return res;
+ return static_cast<int>(a_NumBytes);
+}
+
+
+
+
+void cBlockingSslClientSocket::OnConnected(void)
+{
+ m_IsConnected = true;
+ m_Event.Set();
+}
+
+
+
+
+
+void cBlockingSslClientSocket::OnConnectError(const AString & a_ErrorMsg)
+{
+ LOG("Cannot connect to %s: %s", m_ServerName.c_str(), a_ErrorMsg.c_str());
+ m_Event.Set();
+}
+
+
+
+
+
+void cBlockingSslClientSocket::OnReceivedData(const char * a_Data, size_t a_Size)
+{
+ {
+ cCSLock Lock(m_CSIncomingData);
+ m_IncomingData.append(a_Data, a_Size);
+ }
+ m_Event.Set();
+}
+
+
+
+
+
+void cBlockingSslClientSocket::SetLink(cTCPLinkPtr a_Link)
+{
+ m_Socket = a_Link;
+}
+
+
+
+
+
+void cBlockingSslClientSocket::OnDisconnected(void)
+{
+ m_Socket.reset();
+ m_IsConnected = false;
+ m_Event.Set();
}
diff --git a/src/PolarSSL++/BlockingSslClientSocket.h b/src/PolarSSL++/BlockingSslClientSocket.h
index 7af897582..319e82bf2 100644
--- a/src/PolarSSL++/BlockingSslClientSocket.h
+++ b/src/PolarSSL++/BlockingSslClientSocket.h
@@ -9,8 +9,8 @@
#pragma once
+#include "OSSupport/Network.h"
#include "CallbackSslContext.h"
-#include "../OSSupport/Socket.h"
@@ -51,25 +51,56 @@ public:
const AString & GetLastErrorText(void) const { return m_LastErrorText; }
protected:
+ friend class cBlockingSslClientSocketConnectCallbacks;
+ friend class cBlockingSslClientSocketLinkCallbacks;
+
/** The SSL context used for the socket */
cCallbackSslContext m_Ssl;
/** The underlying socket to the SSL server */
- cSocket m_Socket;
+ cTCPLinkPtr m_Socket;
+
+ /** The object used to signal state changes in the socket (the cause of the blocking). */
+ cEvent m_Event;
/** The trusted CA root cert store, if we are to verify the cert strictly. Set by SetTrustedRootCertsFromString(). */
cX509CertPtr m_CACerts;
/** The expected SSL peer's name, if we are to verify the cert strictly. Set by SetTrustedRootCertsFromString(). */
AString m_ExpectedPeerName;
+
+ /** The hostname to which the socket is connecting (stored for error reporting). */
+ AString m_ServerName;
/** Text of the last error that has occurred. */
AString m_LastErrorText;
/** Set to true if the connection established successfully. */
bool m_IsConnected;
+
+ /** Protects m_IncomingData against multithreaded access. */
+ cCriticalSection m_CSIncomingData;
+
+ /** Buffer for the data incoming on the network socket.
+ Protected by m_CSIncomingData. */
+ AString m_IncomingData;
+ /** Called when the connection is established successfully. */
+ void OnConnected(void);
+
+ /** Called when an error occurs while connecting the socket. */
+ void OnConnectError(const AString & a_ErrorMsg);
+
+ /** Called when there's incoming data from the socket. */
+ void OnReceivedData(const char * a_Data, size_t a_Size);
+
+ /** Called when the link for the connection is created. */
+ void SetLink(cTCPLinkPtr a_Link);
+
+ /** Called when the link is disconnected, either gracefully or by an error. */
+ void OnDisconnected(void);
+
// cCallbackSslContext::cDataCallbacks overrides:
virtual int ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes) override;
virtual int SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes) override;
diff --git a/src/PolarSSL++/SslContext.cpp b/src/PolarSSL++/SslContext.cpp
index 902267f90..66dfefc65 100644
--- a/src/PolarSSL++/SslContext.cpp
+++ b/src/PolarSSL++/SslContext.cpp
@@ -70,7 +70,7 @@ int cSslContext::Initialize(bool a_IsClient, const SharedPtr<cCtrDrbgContext> &
// so they're disabled until someone needs them
ssl_set_dbg(&m_Ssl, &SSLDebugMessage, this);
ssl_set_verify(&m_Ssl, &SSLVerifyCert, this);
- */
+ //*/
/*
// Set ciphersuite to the easiest one to decode, so that the connection can be wireshark-decoded:
diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp
index 3c4e049bd..7d954a297 100644
--- a/src/Protocol/Protocol18x.cpp
+++ b/src/Protocol/Protocol18x.cpp
@@ -108,8 +108,17 @@ cProtocol180::cProtocol180(cClientHandle * a_Client, const AString & a_ServerAdd
{
static int sCounter = 0;
cFile::CreateFolder("CommLogs");
- AString FileName = Printf("CommLogs/%x_%d__%s.log", (unsigned)time(nullptr), sCounter++, a_Client->GetIPString().c_str());
- m_CommLogFile.Open(FileName, cFile::fmWrite);
+ AString IP(a_Client->GetIPString());
+ ReplaceString(IP, ":", "_");
+ AString FileName = Printf("CommLogs/%x_%d__%s.log",
+ static_cast<unsigned>(time(nullptr)),
+ sCounter++,
+ IP.c_str()
+ );
+ if (!m_CommLogFile.Open(FileName, cFile::fmWrite))
+ {
+ LOG("Cannot log communication to file, the log file \"%s\" cannot be opened for writing.", FileName.c_str());
+ }
}
}
@@ -1659,7 +1668,7 @@ void cProtocol180::FixItemFramePositions(int a_ObjectData, double & a_PosX, doub
void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
{
// Write the incoming data into the comm log file:
- if (g_ShouldLogCommIn)
+ if (g_ShouldLogCommIn && m_CommLogFile.IsOpen())
{
if (m_ReceivedData.GetReadableSpace() > 0)
{
@@ -1764,7 +1773,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
bb.Write("\0", 1);
// Log the packet info into the comm log file:
- if (g_ShouldLogCommIn)
+ if (g_ShouldLogCommIn && m_CommLogFile.IsOpen())
{
AString PacketData;
bb.ReadAll(PacketData);
@@ -1796,7 +1805,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
#endif // _DEBUG
// Put a message in the comm log:
- if (g_ShouldLogCommIn)
+ if (g_ShouldLogCommIn && m_CommLogFile.IsOpen())
{
m_CommLogFile.Printf("^^^^^^ Unhandled packet ^^^^^^\n\n\n");
}
@@ -1813,7 +1822,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
);
// Put a message in the comm log:
- if (g_ShouldLogCommIn)
+ if (g_ShouldLogCommIn && m_CommLogFile.IsOpen())
{
m_CommLogFile.Printf("^^^^^^ Wrong number of bytes read for this packet (exp %d left, got " SIZE_T_FMT " left) ^^^^^^\n\n\n",
1, bb.GetReadableSpace()
@@ -1827,7 +1836,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
} // for (ever)
// Log any leftover bytes into the logfile:
- if (g_ShouldLogCommIn && (m_ReceivedData.GetReadableSpace() > 0))
+ if (g_ShouldLogCommIn && (m_ReceivedData.GetReadableSpace() > 0) && m_CommLogFile.IsOpen())
{
AString AllData;
size_t OldReadableSpace = m_ReceivedData.GetReadableSpace();
@@ -2798,7 +2807,7 @@ cProtocol180::cPacketizer::~cPacketizer()
}
// Log the comm into logfile:
- if (g_ShouldLogCommOut)
+ if (g_ShouldLogCommOut && m_Protocol.m_CommLogFile.IsOpen())
{
AString Hex;
ASSERT(PacketData.size() > 0);
diff --git a/src/RCONServer.cpp b/src/RCONServer.cpp
index 49ca4fc61..685bd92f5 100644
--- a/src/RCONServer.cpp
+++ b/src/RCONServer.cpp
@@ -39,13 +39,50 @@ enum
////////////////////////////////////////////////////////////////////////////////
+// cRCONListenCallbacks:
+
+class cRCONListenCallbacks:
+ public cNetwork::cListenCallbacks
+{
+public:
+ cRCONListenCallbacks(cRCONServer & a_RCONServer, UInt16 a_Port):
+ m_RCONServer(a_RCONServer),
+ m_Port(a_Port)
+ {
+ }
+
+protected:
+ /** The RCON server instance that we're attached to. */
+ cRCONServer & m_RCONServer;
+
+ /** The port for which this instance is responsible. */
+ UInt16 m_Port;
+
+ // cNetwork::cListenCallbacks overrides:
+ virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override
+ {
+ LOG("RCON Client \"%s\" connected!", a_RemoteIPAddress.c_str());
+ return std::make_shared<cRCONServer::cConnection>(m_RCONServer, a_RemoteIPAddress);
+ }
+ virtual void OnAccepted(cTCPLink & a_Link) override {}
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
+ {
+ LOGWARNING("RCON server error on port %d: %d (%s)", m_Port, a_ErrorCode, a_ErrorMsg.c_str());
+ }
+};
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
// cRCONCommandOutput:
class cRCONCommandOutput :
public cCommandOutputCallback
{
public:
- cRCONCommandOutput(cRCONServer::cConnection & a_Connection, int a_RequestID) :
+ cRCONCommandOutput(cRCONServer::cConnection & a_Connection, UInt32 a_RequestID) :
m_Connection(a_Connection),
m_RequestID(a_RequestID)
{
@@ -59,13 +96,13 @@ public:
virtual void Finished(void) override
{
- m_Connection.SendResponse(m_RequestID, RCON_PACKET_RESPONSE, (int)m_Buffer.size(), m_Buffer.c_str());
+ m_Connection.SendResponse(m_RequestID, RCON_PACKET_RESPONSE, static_cast<UInt32>(m_Buffer.size()), m_Buffer.c_str());
delete this;
}
protected:
cRCONServer::cConnection & m_Connection;
- int m_RequestID;
+ UInt32 m_RequestID;
AString m_Buffer;
} ;
@@ -77,9 +114,7 @@ protected:
// cRCONServer:
cRCONServer::cRCONServer(cServer & a_Server) :
- m_Server(a_Server),
- m_ListenThread4(*this, cSocket::IPv4, "RCON"),
- m_ListenThread6(*this, cSocket::IPv6, "RCON")
+ m_Server(a_Server)
{
}
@@ -89,8 +124,10 @@ cRCONServer::cRCONServer(cServer & a_Server) :
cRCONServer::~cRCONServer()
{
- m_ListenThread4.Stop();
- m_ListenThread6.Stop();
+ for (auto srv: m_ListenServers)
+ {
+ srv->Close();
+ }
}
@@ -112,24 +149,28 @@ void cRCONServer::Initialize(cIniFile & a_IniFile)
return;
}
- // Read and initialize both IPv4 and IPv6 ports for RCON
- bool HasAnyPorts = false;
- AString Ports4 = a_IniFile.GetValueSet("RCON", "PortsIPv4", "25575");
- if (m_ListenThread4.Initialize(Ports4))
- {
- HasAnyPorts = true;
- m_ListenThread4.Start();
- }
- AString Ports6 = a_IniFile.GetValueSet("RCON", "PortsIPv6", "25575");
- if (m_ListenThread6.Initialize(Ports6))
+ // Read the listening ports for RCON from config:
+ AStringVector Ports = ReadUpgradeIniPorts(a_IniFile, "RCON", "Ports", "PortsIPv4", "PortsIPv6", "25575");
+
+ // Start listening on each specified port:
+ for (auto port: Ports)
{
- HasAnyPorts = true;
- m_ListenThread6.Start();
+ UInt16 PortNum;
+ if (!StringToInteger(port, PortNum))
+ {
+ LOGINFO("Invalid RCON port value: \"%s\". Ignoring.", port.c_str());
+ continue;
+ }
+ auto Handle = cNetwork::Listen(PortNum, std::make_shared<cRCONListenCallbacks>(*this, PortNum));
+ if (Handle->IsListening())
+ {
+ m_ListenServers.push_back(Handle);
+ }
}
- if (!HasAnyPorts)
+
+ if (m_ListenServers.empty())
{
- LOGWARNING("RCON is requested, but no ports are specified. Specify at least one port in PortsIPv4 or PortsIPv6. RCON is now disabled.");
- return;
+ LOGWARNING("RCON is enabled but no valid ports were found. RCON is not accessible.");
}
}
@@ -137,103 +178,92 @@ void cRCONServer::Initialize(cIniFile & a_IniFile)
-void cRCONServer::OnConnectionAccepted(cSocket & a_Socket)
-{
- if (!a_Socket.IsValid())
- {
- return;
- }
+////////////////////////////////////////////////////////////////////////////////
+// cRCONServer::cConnection:
- LOG("RCON Client \"%s\" connected!", a_Socket.GetIPString().c_str());
-
- // Create a new cConnection object, it will be deleted when the connection is closed
- m_SocketThreads.AddClient(a_Socket, new cConnection(*this, a_Socket));
+cRCONServer::cConnection::cConnection(cRCONServer & a_RCONServer, const AString & a_IPAddress) :
+ m_IsAuthenticated(false),
+ m_RCONServer(a_RCONServer),
+ m_IPAddress(a_IPAddress)
+{
}
-////////////////////////////////////////////////////////////////////////////////
-// cRCONServer::cConnection:
-
-cRCONServer::cConnection::cConnection(cRCONServer & a_RCONServer, cSocket & a_Socket) :
- m_IsAuthenticated(false),
- m_RCONServer(a_RCONServer),
- m_Socket(a_Socket),
- m_IPAddress(a_Socket.GetIPString())
+void cRCONServer::cConnection::OnLinkCreated(cTCPLinkPtr a_Link)
{
+ m_Link = a_Link;
}
-bool cRCONServer::cConnection::DataReceived(const char * a_Data, size_t a_Size)
+void cRCONServer::cConnection::OnReceivedData(const char * a_Data, size_t a_Size)
{
+ ASSERT(m_Link != nullptr);
+
// Append data to the buffer:
m_Buffer.append(a_Data, a_Size);
// Process the packets in the buffer:
while (m_Buffer.size() >= 14)
{
- int Length = IntFromBuffer(m_Buffer.data());
+ UInt32 Length = UIntFromBuffer(m_Buffer.data());
if (Length > 1500)
{
// Too long, drop the connection
LOGWARNING("Received an invalid RCON packet length (%d), dropping RCON connection to %s.",
Length, m_IPAddress.c_str()
);
- m_RCONServer.m_SocketThreads.RemoveClient(this);
- m_Socket.CloseSocket();
- delete this;
- return false;
+ m_Link->Close();
+ m_Link.reset();
+ return;
}
- if (Length > (int)(m_Buffer.size() + 4))
+ if (Length > static_cast<UInt32>(m_Buffer.size() + 4))
{
// Incomplete packet yet, wait for more data to come
- return false;
+ return;
}
- int RequestID = IntFromBuffer(m_Buffer.data() + 4);
- int PacketType = IntFromBuffer(m_Buffer.data() + 8);
+ UInt32 RequestID = UIntFromBuffer(m_Buffer.data() + 4);
+ UInt32 PacketType = UIntFromBuffer(m_Buffer.data() + 8);
if (!ProcessPacket(RequestID, PacketType, Length - 10, m_Buffer.data() + 12))
{
- m_RCONServer.m_SocketThreads.RemoveClient(this);
- m_Socket.CloseSocket();
- delete this;
- return false;
+ m_Link->Close();
+ m_Link.reset();
+ return;
}
m_Buffer.erase(0, Length + 4);
} // while (m_Buffer.size() >= 14)
- return false;
}
-void cRCONServer::cConnection::GetOutgoingData(AString & a_Data)
+void cRCONServer::cConnection::OnRemoteClosed(void)
{
- a_Data.assign(m_Outgoing);
- m_Outgoing.clear();
+ m_Link.reset();
}
-void cRCONServer::cConnection::SocketClosed(void)
+void cRCONServer::cConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
{
- m_RCONServer.m_SocketThreads.RemoveClient(this);
- delete this;
+ LOGD("Error in RCON connection %s: %d (%s)", m_IPAddress.c_str(), a_ErrorCode, a_ErrorMsg.c_str());
+ m_Link.reset();
}
-bool cRCONServer::cConnection::ProcessPacket(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload)
+bool cRCONServer::cConnection::ProcessPacket(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload)
{
switch (a_PacketType)
{
@@ -242,7 +272,7 @@ bool cRCONServer::cConnection::ProcessPacket(int a_RequestID, int a_PacketType,
if (strncmp(a_Payload, m_RCONServer.m_Password.c_str(), a_PayloadLength) != 0)
{
LOGINFO("RCON: Invalid password from client %s, dropping connection.", m_IPAddress.c_str());
- SendResponse(-1, RCON_PACKET_RESPONSE, 0, nullptr);
+ SendResponse(0xffffffffU, RCON_PACKET_RESPONSE, 0, nullptr);
return false;
}
m_IsAuthenticated = true;
@@ -284,23 +314,22 @@ bool cRCONServer::cConnection::ProcessPacket(int a_RequestID, int a_PacketType,
-/// Reads 4 bytes from a_Buffer and returns the int they represent
-int cRCONServer::cConnection::IntFromBuffer(const char * a_Buffer)
+UInt32 cRCONServer::cConnection::UIntFromBuffer(const char * a_Buffer)
{
- return ((unsigned char)a_Buffer[3] << 24) | ((unsigned char)a_Buffer[2] << 16) | ((unsigned char)a_Buffer[1] << 8) | (unsigned char)a_Buffer[0];
+ const Byte * Buffer = reinterpret_cast<const Byte *>(a_Buffer);
+ return (Buffer[3] << 24) | (Buffer[2] << 16) | (Buffer[1] << 8) | Buffer[0];
}
-/// Puts 4 bytes representing the int into the buffer
-void cRCONServer::cConnection::IntToBuffer(int a_Value, char * a_Buffer)
+void cRCONServer::cConnection::UIntToBuffer(UInt32 a_Value, char * a_Buffer)
{
- a_Buffer[0] = a_Value & 0xff;
- a_Buffer[1] = (a_Value >> 8) & 0xff;
- a_Buffer[2] = (a_Value >> 16) & 0xff;
- a_Buffer[3] = (a_Value >> 24) & 0xff;
+ a_Buffer[0] = static_cast<char>(a_Value & 0xff);
+ a_Buffer[1] = static_cast<char>((a_Value >> 8) & 0xff);
+ a_Buffer[2] = static_cast<char>((a_Value >> 16) & 0xff);
+ a_Buffer[3] = static_cast<char>((a_Value >> 24) & 0xff);
}
@@ -308,25 +337,22 @@ void cRCONServer::cConnection::IntToBuffer(int a_Value, char * a_Buffer)
/// Sends a RCON packet back to the client
-void cRCONServer::cConnection::SendResponse(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload)
+void cRCONServer::cConnection::SendResponse(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload)
{
ASSERT((a_PayloadLength == 0) || (a_Payload != nullptr)); // Either zero data to send, or a valid payload ptr
+ ASSERT(m_Link != nullptr);
- char Buffer[4];
- int Length = a_PayloadLength + 10;
- IntToBuffer(Length, Buffer);
- m_Outgoing.append(Buffer, 4);
- IntToBuffer(a_RequestID, Buffer);
- m_Outgoing.append(Buffer, 4);
- IntToBuffer(a_PacketType, Buffer);
- m_Outgoing.append(Buffer, 4);
+ char Buffer[12];
+ UInt32 Length = a_PayloadLength + 10;
+ UIntToBuffer(Length, Buffer);
+ UIntToBuffer(a_RequestID, Buffer + 4);
+ UIntToBuffer(a_PacketType, Buffer + 8);
+ m_Link->Send(Buffer, 12);
if (a_PayloadLength > 0)
{
- m_Outgoing.append(a_Payload, a_PayloadLength);
+ m_Link->Send(a_Payload, a_PayloadLength);
}
- m_Outgoing.push_back(0);
- m_Outgoing.push_back(0);
- m_RCONServer.m_SocketThreads.NotifyWrite(this);
+ m_Link->Send("\0", 2); // Send two zero chars as the padding
}
diff --git a/src/RCONServer.h b/src/RCONServer.h
index 47c746736..352fa7b50 100644
--- a/src/RCONServer.h
+++ b/src/RCONServer.h
@@ -9,8 +9,7 @@
#pragma once
-#include "OSSupport/SocketThreads.h"
-#include "OSSupport/ListenThread.h"
+#include "OSSupport/Network.h"
@@ -24,8 +23,7 @@ class cIniFile;
-class cRCONServer :
- public cListenThread::cCallback
+class cRCONServer
{
public:
cRCONServer(cServer & a_Server);
@@ -35,72 +33,61 @@ public:
protected:
friend class cRCONCommandOutput;
+ friend class cRCONListenCallbacks;
class cConnection :
- public cSocketThreads::cCallback
+ public cTCPLink::cCallbacks
{
public:
- cConnection(cRCONServer & a_RCONServer, cSocket & a_Socket);
+ cConnection(cRCONServer & a_RCONServer, const AString & a_IPAddress);
protected:
friend class cRCONCommandOutput;
- /// Set to true if the client has successfully authenticated
+ /** Set to true if the client has successfully authenticated */
bool m_IsAuthenticated;
- /// Buffer for the incoming data
+ /** Buffer for the incoming data */
AString m_Buffer;
- /// Buffer for the outgoing data
- AString m_Outgoing;
-
- /// Server that owns this connection and processes requests
+ /** Server that owns this connection and processes requests */
cRCONServer & m_RCONServer;
- /// The socket belonging to the client
- cSocket & m_Socket;
+ /** The TCP link to the client */
+ cTCPLinkPtr m_Link;
- /// Address of the client
+ /** Address of the client */
AString m_IPAddress;
- // cSocketThreads::cCallback overrides:
- virtual bool DataReceived(const char * a_Data, size_t a_Size) override;
- virtual void GetOutgoingData(AString & a_Data) override;
- virtual void SocketClosed(void) override;
+ // cTCPLink::cCallbacks overrides:
+ virtual void OnLinkCreated(cTCPLinkPtr a_Link);
+ virtual void OnReceivedData(const char * a_Data, size_t a_Length) override;
+ virtual void OnRemoteClosed(void) override;
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
- /// Processes the given packet and sends the response; returns true if successful, false if the connection is to be dropped
- bool ProcessPacket(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload);
+ /** Processes the given packet and sends the response; returns true if successful, false if the connection is to be dropped */
+ bool ProcessPacket(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload);
- /// Reads 4 bytes from a_Buffer and returns the int they represent
- int IntFromBuffer(const char * a_Buffer);
+ /** Reads 4 bytes from a_Buffer and returns the LE UInt32 they represent */
+ UInt32 UIntFromBuffer(const char * a_Buffer);
- /// Puts 4 bytes representing the int into the buffer
- void IntToBuffer(int a_Value, char * a_Buffer);
+ /** Puts 4 bytes representing the int into the buffer */
+ void UIntToBuffer(UInt32 a_Value, char * a_Buffer);
- /// Sends a RCON packet back to the client
- void SendResponse(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload);
+ /** Sends a RCON packet back to the client */
+ void SendResponse(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload);
} ;
- /// The server object that will process the commands received
+ /** The server object that will process the commands received */
cServer & m_Server;
- /// The thread(s) that take care of all the traffic on the RCON ports
- cSocketThreads m_SocketThreads;
-
- /// The thread for accepting IPv4 RCON connections
- cListenThread m_ListenThread4;
-
- /// The thread for accepting IPv6 RCON connections
- cListenThread m_ListenThread6;
+ /** The sockets for accepting RCON connections (one socket per port). */
+ cServerHandlePtrs m_ListenServers;
- /// Password for authentication
+ /** Password for authentication */
AString m_Password;
-
-
- // cListenThread::cCallback overrides:
- virtual void OnConnectionAccepted(cSocket & a_Socket) override;
} ;
diff --git a/src/Root.cpp b/src/Root.cpp
index eaacf3608..27d87c717 100644
--- a/src/Root.cpp
+++ b/src/Root.cpp
@@ -181,43 +181,49 @@ void cRoot::Start(void)
IniFile.WriteFile("settings.ini");
LOGD("Finalising startup...");
- m_Server->Start();
-
- m_WebAdmin->Start();
-
- #if !defined(ANDROID_NDK)
- LOGD("Starting InputThread...");
- try
+ if (m_Server->Start())
{
- m_InputThread = std::thread(InputThread, std::ref(*this));
- m_InputThread.detach();
- }
- catch (std::system_error & a_Exception)
- {
- LOGERROR("cRoot::Start (std::thread) error %i: could not construct input thread; %s", a_Exception.code().value(), a_Exception.what());
- }
- #endif
+ m_WebAdmin->Start();
- LOG("Startup complete, took %ldms!", static_cast<long int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - BeginTime).count()));
- #ifdef _WIN32
- EnableMenuItem(hmenu, SC_CLOSE, MF_ENABLED); // Re-enable close button
- #endif
+ #if !defined(ANDROID_NDK)
+ LOGD("Starting InputThread...");
+ try
+ {
+ m_InputThread = std::thread(InputThread, std::ref(*this));
+ m_InputThread.detach();
+ }
+ catch (std::system_error & a_Exception)
+ {
+ LOGERROR("cRoot::Start (std::thread) error %i: could not construct input thread; %s", a_Exception.code().value(), a_Exception.what());
+ }
+ #endif
- while (!m_bStop && !m_bRestart && !m_TerminateEventRaised) // These are modified by external threads
- {
- std::this_thread::sleep_for(std::chrono::seconds(1));
- }
+ LOG("Startup complete, took %ldms!", static_cast<long int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - BeginTime).count()));
+ #ifdef _WIN32
+ EnableMenuItem(hmenu, SC_CLOSE, MF_ENABLED); // Re-enable close button
+ #endif
- if (m_TerminateEventRaised)
+ while (!m_bStop && !m_bRestart && !m_TerminateEventRaised) // These are modified by external threads
+ {
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+
+ if (m_TerminateEventRaised)
+ {
+ m_bStop = true;
+ }
+
+ // Stop the server:
+ m_WebAdmin->Stop();
+
+ LOG("Shutting down server...");
+ m_Server->Shutdown();
+ } // if (m_Server->Start())
+ else
{
m_bStop = true;
}
- // Stop the server:
- m_WebAdmin->Stop();
-
- LOG("Shutting down server...");
- m_Server->Shutdown();
delete m_MojangAPI; m_MojangAPI = nullptr;
LOGD("Shutting down deadlock detector...");
diff --git a/src/Server.cpp b/src/Server.cpp
index 4dbe59ac6..3f61be378 100644
--- a/src/Server.cpp
+++ b/src/Server.cpp
@@ -5,7 +5,6 @@
#include "Server.h"
#include "ClientHandle.h"
#include "Mobs/Monster.h"
-#include "OSSupport/Socket.h"
#include "Root.h"
#include "World.h"
#include "ChunkDef.h"
@@ -58,6 +57,39 @@ typedef std::list< cClientHandle* > ClientList;
////////////////////////////////////////////////////////////////////////////////
+// cServerListenCallbacks:
+
+class cServerListenCallbacks:
+ public cNetwork::cListenCallbacks
+{
+ cServer & m_Server;
+ UInt16 m_Port;
+
+ virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override
+ {
+ return m_Server.OnConnectionAccepted(a_RemoteIPAddress);
+ }
+
+ virtual void OnAccepted(cTCPLink & a_Link) override {}
+
+ virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg)
+ {
+ LOGWARNING("Cannot listen on port %d: %d (%s).", m_Port, a_ErrorCode, a_ErrorMsg.c_str());
+ }
+
+public:
+ cServerListenCallbacks(cServer & a_Server, UInt16 a_Port):
+ m_Server(a_Server),
+ m_Port(a_Port)
+ {
+ }
+};
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
// cServer::cTickThread:
cServer::cTickThread::cTickThread(cServer & a_Server) :
@@ -100,8 +132,6 @@ void cServer::cTickThread::Execute(void)
// cServer:
cServer::cServer(void) :
- m_ListenThreadIPv4(*this, cSocket::IPv4, "Client"),
- m_ListenThreadIPv6(*this, cSocket::IPv6, "Client"),
m_PlayerCount(0),
m_PlayerCountDiff(0),
m_ClientViewDistance(0),
@@ -121,42 +151,6 @@ cServer::cServer(void) :
-void cServer::ClientDestroying(const cClientHandle * a_Client)
-{
- m_SocketThreads.RemoveClient(a_Client);
-}
-
-
-
-
-
-void cServer::NotifyClientWrite(const cClientHandle * a_Client)
-{
- m_NotifyWriteThread.NotifyClientWrite(a_Client);
-}
-
-
-
-
-
-void cServer::WriteToClient(const cClientHandle * a_Client, const AString & a_Data)
-{
- m_SocketThreads.Write(a_Client, a_Data);
-}
-
-
-
-
-
-void cServer::RemoveClient(const cClientHandle * a_Client)
-{
- m_SocketThreads.RemoveClient(a_Client);
-}
-
-
-
-
-
void cServer::ClientMovedToWorld(const cClientHandle * a_Client)
{
cCSLock Lock(m_CSClients);
@@ -211,33 +205,8 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth)
LOGINFO("Compatible clients: %s", MCS_CLIENT_VERSIONS);
LOGINFO("Compatible protocol versions %s", MCS_PROTOCOL_VERSIONS);
- if (cSocket::WSAStartup() != 0) // Only does anything on Windows, but whatever
- {
- LOGERROR("WSAStartup() != 0");
- return false;
- }
-
- bool HasAnyPorts = false;
- AString Ports = a_SettingsIni.GetValueSet("Server", "Port", "25565");
- m_ListenThreadIPv4.SetReuseAddr(true);
- if (m_ListenThreadIPv4.Initialize(Ports))
- {
- HasAnyPorts = true;
- }
-
- Ports = a_SettingsIni.GetValueSet("Server", "PortsIPv6", "25565");
- m_ListenThreadIPv6.SetReuseAddr(true);
- if (m_ListenThreadIPv6.Initialize(Ports))
- {
- HasAnyPorts = true;
- }
+ m_Ports = ReadUpgradeIniPorts(a_SettingsIni, "Server", "Ports", "Port", "PortsIPv6", "25565");
- if (!HasAnyPorts)
- {
- LOGERROR("Couldn't open any ports. Aborting the server");
- return false;
- }
-
m_RCONServer.Initialize(a_SettingsIni);
m_bIsConnected = true;
@@ -278,8 +247,6 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth)
LOGINFO("Setting default viewdistance to the maximum of %d", m_ClientViewDistance);
}
- m_NotifyWriteThread.Start(this);
-
PrepareKeys();
return true;
@@ -327,36 +294,14 @@ void cServer::PrepareKeys(void)
-void cServer::OnConnectionAccepted(cSocket & a_Socket)
+cTCPLink::cCallbacksPtr cServer::OnConnectionAccepted(const AString & a_RemoteIPAddress)
{
- if (!a_Socket.IsValid())
- {
- return;
- }
-
- const AString & ClientIP = a_Socket.GetIPString();
- if (ClientIP.empty())
- {
- LOGWARN("cServer: A client connected, but didn't present its IP, disconnecting.");
- a_Socket.CloseSocket();
- return;
- }
-
- LOGD("Client \"%s\" connected!", ClientIP.c_str());
-
- cClientHandle * NewHandle = new cClientHandle(&a_Socket, m_ClientViewDistance);
- if (!m_SocketThreads.AddClient(a_Socket, NewHandle))
- {
- // For some reason SocketThreads have rejected the handle, clean it up
- LOGERROR("Client \"%s\" cannot be handled, server probably unstable", ClientIP.c_str());
- a_Socket.CloseSocket();
- delete NewHandle;
- NewHandle = nullptr;
- return;
- }
-
+ LOGD("Client \"%s\" connected!", a_RemoteIPAddress.c_str());
+ cClientHandlePtr NewHandle = std::make_shared<cClientHandle>(a_RemoteIPAddress, m_ClientViewDistance);
+ NewHandle->SetSelf(NewHandle);
cCSLock Lock(m_CSClients);
m_Clients.push_back(NewHandle);
+ return NewHandle;
}
@@ -403,23 +348,30 @@ bool cServer::Tick(float a_Dt)
void cServer::TickClients(float a_Dt)
{
- cClientHandleList RemoveClients;
+ cClientHandlePtrs RemoveClients;
{
cCSLock Lock(m_CSClients);
// Remove clients that have moved to a world (the world will be ticking them from now on)
- for (cClientHandleList::const_iterator itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
+ for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
{
- m_Clients.remove(*itr);
+ for (auto itrC = m_Clients.begin(), endC = m_Clients.end(); itrC != endC; ++itrC)
+ {
+ if (itrC->get() == *itr)
+ {
+ m_Clients.erase(itrC);
+ break;
+ }
+ }
} // for itr - m_ClientsToRemove[]
m_ClientsToRemove.clear();
// Tick the remaining clients, take out those that have been destroyed into RemoveClients
- for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();)
+ for (auto itr = m_Clients.begin(); itr != m_Clients.end();)
{
if ((*itr)->IsDestroyed())
{
- // Remove the client later, when CS is not held, to avoid deadlock: http://forum.mc-server.org/showthread.php?tid=374
+ // Delete the client later, when CS is not held, to avoid deadlock: http://forum.mc-server.org/showthread.php?tid=374
RemoveClients.push_back(*itr);
itr = m_Clients.erase(itr);
continue;
@@ -430,10 +382,7 @@ void cServer::TickClients(float a_Dt)
}
// Delete the clients that have been destroyed
- for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr)
- {
- delete *itr;
- } // for itr - RemoveClients[]
+ RemoveClients.clear();
}
@@ -442,12 +391,23 @@ void cServer::TickClients(float a_Dt)
bool cServer::Start(void)
{
- if (!m_ListenThreadIPv4.Start())
+ for (auto port: m_Ports)
{
- return false;
- }
- if (!m_ListenThreadIPv6.Start())
+ UInt16 PortNum;
+ if (!StringToInteger(port, PortNum))
+ {
+ LOGWARNING("Invalid port specified for server: \"%s\". Ignoring.", port.c_str());
+ continue;
+ }
+ auto Handle = cNetwork::Listen(PortNum, std::make_shared<cServerListenCallbacks>(*this, PortNum));
+ if (Handle->IsListening())
+ {
+ m_ServerHandles.push_back(Handle);
+ }
+ } // for port - Ports[]
+ if (m_ServerHandles.empty())
{
+ LOGERROR("Couldn't open any ports. Aborting the server");
return false;
}
if (!m_TickThread.Start())
@@ -640,7 +600,6 @@ void cServer::PrintHelp(const AStringVector & a_Split, cCommandOutputCallback &
const AStringPair & cmd = *itr;
a_Output.Out(Printf("%-*s%s\n", static_cast<int>(Callback.m_MaxLen), cmd.first.c_str(), cmd.second.c_str()));
} // for itr - Callback.m_Commands[]
- a_Output.Finished();
}
@@ -670,19 +629,24 @@ void cServer::BindBuiltInConsoleCommands(void)
void cServer::Shutdown(void)
{
- m_ListenThreadIPv4.Stop();
- m_ListenThreadIPv6.Stop();
+ // Stop listening on all sockets:
+ for (auto srv: m_ServerHandles)
+ {
+ srv->Close();
+ }
+ m_ServerHandles.clear();
+ // Notify the tick thread and wait for it to terminate:
m_bRestarting = true;
m_RestartEvent.Wait();
cRoot::Get()->SaveAllChunks();
+ // Remove all clients:
cCSLock Lock(m_CSClients);
- for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
+ for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
{
(*itr)->Destroy();
- delete *itr;
}
m_Clients.clear();
}
@@ -694,7 +658,7 @@ void cServer::Shutdown(void)
void cServer::KickUser(int a_ClientID, const AString & a_Reason)
{
cCSLock Lock(m_CSClients);
- for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
+ for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
{
if ((*itr)->GetUniqueID() == a_ClientID)
{
@@ -710,7 +674,7 @@ void cServer::KickUser(int a_ClientID, const AString & a_Reason)
void cServer::AuthenticateUser(int a_ClientID, const AString & a_Name, const AString & a_UUID, const Json::Value & a_Properties)
{
cCSLock Lock(m_CSClients);
- for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
+ for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
{
if ((*itr)->GetUniqueID() == a_ClientID)
{
@@ -724,82 +688,3 @@ void cServer::AuthenticateUser(int a_ClientID, const AString & a_Name, const ASt
-////////////////////////////////////////////////////////////////////////////////
-// cServer::cNotifyWriteThread:
-
-cServer::cNotifyWriteThread::cNotifyWriteThread(void) :
- super("ClientPacketThread"),
- m_Server(nullptr)
-{
-}
-
-
-
-
-
-cServer::cNotifyWriteThread::~cNotifyWriteThread()
-{
- m_ShouldTerminate = true;
- m_Event.Set();
- Wait();
-}
-
-
-
-
-
-bool cServer::cNotifyWriteThread::Start(cServer * a_Server)
-{
- m_Server = a_Server;
- return super::Start();
-}
-
-
-
-
-
-void cServer::cNotifyWriteThread::Execute(void)
-{
- cClientHandleList Clients;
- while (!m_ShouldTerminate)
- {
- cCSLock Lock(m_CS);
- while (m_Clients.empty())
- {
- cCSUnlock Unlock(Lock);
- m_Event.Wait();
- if (m_ShouldTerminate)
- {
- return;
- }
- }
-
- // Copy the clients to notify and unlock the CS:
- Clients.splice(Clients.begin(), m_Clients);
- Lock.Unlock();
-
- for (cClientHandleList::iterator itr = Clients.begin(); itr != Clients.end(); ++itr)
- {
- m_Server->m_SocketThreads.NotifyWrite(*itr);
- } // for itr - Clients[]
- Clients.clear();
- } // while (!mShouldTerminate)
-}
-
-
-
-
-
-void cServer::cNotifyWriteThread::NotifyClientWrite(const cClientHandle * a_Client)
-{
- {
- cCSLock Lock(m_CS);
- m_Clients.remove(const_cast<cClientHandle *>(a_Client)); // Put it there only once
- m_Clients.push_back(const_cast<cClientHandle *>(a_Client));
- }
- m_Event.Set();
-}
-
-
-
-
diff --git a/src/Server.h b/src/Server.h
index aab47987f..1f30295b7 100644
--- a/src/Server.h
+++ b/src/Server.h
@@ -9,10 +9,9 @@
#pragma once
-#include "OSSupport/SocketThreads.h"
-#include "OSSupport/ListenThread.h"
-
#include "RCONServer.h"
+#include "OSSupport/IsThread.h"
+#include "OSSupport/Network.h"
#ifdef _MSC_VER
#pragma warning(push)
@@ -36,10 +35,12 @@
// fwd:
class cPlayer;
class cClientHandle;
+typedef SharedPtr<cClientHandle> cClientHandlePtr;
+typedef std::list<cClientHandlePtr> cClientHandlePtrs;
+typedef std::list<cClientHandle *> cClientHandles;
class cIniFile;
class cCommandOutputCallback;
-typedef std::list<cClientHandle *> cClientHandleList;
namespace Json
{
@@ -50,10 +51,11 @@ namespace Json
-class cServer // tolua_export
- : public cListenThread::cCallback
-{ // tolua_export
-public: // tolua_export
+// tolua_begin
+class cServer
+{
+public:
+ // tolua_end
virtual ~cServer() {}
bool InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth);
@@ -105,13 +107,6 @@ public: // tolua_export
/** Called by cClientHandle's destructor; stop m_SocketThreads from calling back into a_Client */
void ClientDestroying(const cClientHandle * a_Client);
- /** Notifies m_SocketThreads that client has something to be written */
- void NotifyClientWrite(const cClientHandle * a_Client);
-
- void WriteToClient(const cClientHandle * a_Client, const AString & a_Data); // Queues outgoing data for the client through m_SocketThreads
-
- void RemoveClient(const cClientHandle * a_Client); // Removes the clienthandle from m_SocketThreads
-
/** Don't tick a_Client anymore, it will be ticked from its cPlayer instead */
void ClientMovedToWorld(const cClientHandle * a_Client);
@@ -147,30 +142,7 @@ public: // tolua_export
private:
friend class cRoot; // so cRoot can create and destroy cServer
-
- /** When NotifyClientWrite() is called, it is queued for this thread to process (to avoid deadlocks between cSocketThreads, cClientHandle and cChunkMap) */
- class cNotifyWriteThread :
- public cIsThread
- {
- typedef cIsThread super;
-
- cEvent m_Event; // Set when m_Clients gets appended
- cServer * m_Server;
-
- cCriticalSection m_CS;
- cClientHandleList m_Clients;
-
- virtual void Execute(void);
-
- public:
-
- cNotifyWriteThread(void);
- ~cNotifyWriteThread();
-
- bool Start(cServer * a_Server);
-
- void NotifyClientWrite(const cClientHandle * a_Client);
- } ;
+ friend class cServerListenCallbacks; // Accessing OnConnectionAccepted()
/** The server tick thread takes care of the players who aren't yet spawned in a world */
class cTickThread :
@@ -189,21 +161,29 @@ private:
} ;
- cNotifyWriteThread m_NotifyWriteThread;
-
- cListenThread m_ListenThreadIPv4;
- cListenThread m_ListenThreadIPv6;
-
- cCriticalSection m_CSClients; ///< Locks client lists
- cClientHandleList m_Clients; ///< Clients that are connected to the server and not yet assigned to a cWorld
- cClientHandleList m_ClientsToRemove; ///< Clients that have just been moved into a world and are to be removed from m_Clients in the next Tick()
-
- mutable cCriticalSection m_CSPlayerCount; ///< Locks the m_PlayerCount
- int m_PlayerCount; ///< Number of players currently playing in the server
- cCriticalSection m_CSPlayerCountDiff; ///< Locks the m_PlayerCountDiff
- int m_PlayerCountDiff; ///< Adjustment to m_PlayerCount to be applied in the Tick thread
+ /** The network sockets listening for client connections. */
+ cServerHandlePtrs m_ServerHandles;
+
+ /** Protects m_Clients and m_ClientsToRemove against multithreaded access. */
+ cCriticalSection m_CSClients;
+
+ /** Clients that are connected to the server and not yet assigned to a cWorld. */
+ cClientHandlePtrs m_Clients;
+
+ /** Clients that have just been moved into a world and are to be removed from m_Clients in the next Tick(). */
+ cClientHandles m_ClientsToRemove;
- cSocketThreads m_SocketThreads;
+ /** Protects m_PlayerCount against multithreaded access. */
+ mutable cCriticalSection m_CSPlayerCount;
+
+ /** Number of players currently playing in the server. */
+ int m_PlayerCount;
+
+ /** Protects m_PlayerCountDiff against multithreaded access. */
+ cCriticalSection m_CSPlayerCountDiff;
+
+ /** Adjustment to m_PlayerCount to be applied in the Tick thread. */
+ int m_PlayerCountDiff;
int m_ClientViewDistance; // The default view distance for clients; settable in Settings.ini
@@ -250,19 +230,24 @@ private:
/** True if BungeeCord handshake packets (with player UUID) should be accepted. */
bool m_ShouldAllowBungeeCord;
+ /** The list of ports on which the server should listen for connections.
+ Initialized in InitServer(), used in Start(). */
+ AStringVector m_Ports;
+
cServer(void);
/** Loads, or generates, if missing, RSA keys for protocol encryption */
void PrepareKeys(void);
+
+ /** Creates a new cClientHandle instance and adds it to the list of clients.
+ Returns the cClientHandle reinterpreted as cTCPLink callbacks. */
+ cTCPLink::cCallbacksPtr OnConnectionAccepted(const AString & a_RemoteIPAddress);
bool Tick(float a_Dt);
/** Ticks the clients in m_Clients, manages the list in respect to removing clients */
void TickClients(float a_Dt);
-
- // cListenThread::cCallback overrides:
- virtual void OnConnectionAccepted(cSocket & a_Socket) override;
}; // tolua_export
diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp
index a63525356..4eb2d48b6 100644
--- a/src/StringUtils.cpp
+++ b/src/StringUtils.cpp
@@ -905,3 +905,47 @@ bool SplitZeroTerminatedStrings(const AString & a_Strings, AStringVector & a_Out
+
+AStringVector MergeStringVectors(const AStringVector & a_Strings1, const AStringVector & a_Strings2)
+{
+ // Initialize the resulting vector by the first vector:
+ AStringVector res = a_Strings1;
+
+ // Add each item from strings2 that is not already present:
+ for (auto item : a_Strings2)
+ {
+ if (std::find(res.begin(), res.end(), item) == res.end())
+ {
+ res.push_back(item);
+ }
+ } // for item - a_Strings2[]
+
+ return res;
+}
+
+
+
+
+
+AString StringsConcat(const AStringVector & a_Strings, char a_Separator)
+{
+ // If the vector is empty, return an empty string:
+ if (a_Strings.empty())
+ {
+ return "";
+ }
+
+ // Concatenate the strings in the vector:
+ AString res;
+ res.append(a_Strings[0]);
+ for (auto itr = a_Strings.cbegin() + 1, end = a_Strings.cend(); itr != end; ++itr)
+ {
+ res.push_back(a_Separator);
+ res.append(*itr);
+ }
+ return res;
+}
+
+
+
+
diff --git a/src/StringUtils.h b/src/StringUtils.h
index bfe2a41fa..bc3bb7a2c 100644
--- a/src/StringUtils.h
+++ b/src/StringUtils.h
@@ -115,7 +115,16 @@ a_Output is first cleared and then each separate string is pushed back into a_Ou
Returns true if there are at least two strings in a_Output (there was at least one \0 separator). */
extern bool SplitZeroTerminatedStrings(const AString & a_Strings, AStringVector & a_Output);
-/// Parses any integer type. Checks bounds and returns errors out of band.
+/** Merges the two vectors of strings, removing duplicate entries from the second vector.
+The resulting vector contains items from a_Strings1 first, then from a_Strings2.
+The order of items doesn't change, only the duplicates are removed.
+If a_Strings1 contains duplicates, the result will still contain those duplicates. */
+extern AStringVector MergeStringVectors(const AStringVector & a_Strings1, const AStringVector & a_Strings2);
+
+/** Concatenates the specified strings into a single string, separated by the specified separator. */
+extern AString StringsConcat(const AStringVector & a_Strings, char a_Separator);
+
+/** Parses any integer type. Checks bounds and returns errors out of band. */
template <class T>
bool StringToInteger(const AString & a_str, T & a_Num)
{
diff --git a/src/WebAdmin.cpp b/src/WebAdmin.cpp
index dbf600c25..13cf3cc41 100644
--- a/src/WebAdmin.cpp
+++ b/src/WebAdmin.cpp
@@ -19,7 +19,16 @@
-/// Helper class - appends all player names together in a HTML list
+static const char DEFAULT_WEBADMIN_PORTS[] = "8080";
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cPlayerAccum:
+
+/** Helper class - appends all player names together in an HTML list */
class cPlayerAccum :
public cPlayerListCallback
{
@@ -40,11 +49,12 @@ public:
+////////////////////////////////////////////////////////////////////////////////
+// cWebAdmin:
+
cWebAdmin::cWebAdmin(void) :
m_IsInitialized(false),
m_IsRunning(false),
- m_PortsIPv4("8080"),
- m_PortsIPv6(""),
m_TemplateScript("<webadmin_template>")
{
}
@@ -91,8 +101,7 @@ bool cWebAdmin::Init(void)
m_IniFile.AddHeaderComment(" Password format: Password=*password*; for example:");
m_IniFile.AddHeaderComment(" [User:admin]");
m_IniFile.AddHeaderComment(" Password=admin");
- m_IniFile.SetValue("WebAdmin", "Port", m_PortsIPv4);
- m_IniFile.SetValue("WebAdmin", "PortsIPv6", m_PortsIPv6);
+ m_IniFile.SetValue("WebAdmin", "Ports", DEFAULT_WEBADMIN_PORTS);
m_IniFile.WriteFile("webadmin.ini");
}
@@ -104,32 +113,6 @@ bool cWebAdmin::Init(void)
LOGD("Initialising WebAdmin...");
- m_PortsIPv4 = m_IniFile.GetValueSet("WebAdmin", "Port", m_PortsIPv4);
- m_PortsIPv6 = m_IniFile.GetValueSet("WebAdmin", "PortsIPv6", m_PortsIPv6);
-
- if (!m_HTTPServer.Initialize(m_PortsIPv4, m_PortsIPv6))
- {
- return false;
- }
- m_IsInitialized = true;
- m_IniFile.WriteFile("webadmin.ini");
- return true;
-}
-
-
-
-
-
-bool cWebAdmin::Start(void)
-{
- if (!m_IsInitialized)
- {
- // Not initialized
- return false;
- }
-
- LOGD("Starting WebAdmin...");
-
// Initialize the WebAdmin template script and load the file
m_TemplateScript.Create();
m_TemplateScript.RegisterAPILibs();
@@ -141,6 +124,7 @@ bool cWebAdmin::Start(void)
return false;
}
+ // Load the login template, provide a fallback default if not found:
if (!LoadLoginTemplate())
{
LOGWARN("Could not load WebAdmin login template \"%s\", using fallback template.", FILE_IO_PREFIX "webadmin/login_template.html");
@@ -155,7 +139,34 @@ bool cWebAdmin::Start(void)
"</center>";
}
- m_IsRunning = m_HTTPServer.Start(*this);
+ // Read the ports to be used:
+ // Note that historically the ports were stored in the "Port" and "PortsIPv6" values
+ m_Ports = ReadUpgradeIniPorts(m_IniFile, "WebAdmin", "Ports", "Port", "PortsIPv6", DEFAULT_WEBADMIN_PORTS);
+
+ if (!m_HTTPServer.Initialize())
+ {
+ return false;
+ }
+ m_IsInitialized = true;
+ m_IniFile.WriteFile("webadmin.ini");
+ return true;
+}
+
+
+
+
+
+bool cWebAdmin::Start(void)
+{
+ if (!m_IsInitialized)
+ {
+ // Not initialized
+ return false;
+ }
+
+ LOGD("Starting WebAdmin...");
+
+ m_IsRunning = m_HTTPServer.Start(*this, m_Ports);
return m_IsRunning;
}
diff --git a/src/WebAdmin.h b/src/WebAdmin.h
index a85fb1f0c..86a8a9a4b 100644
--- a/src/WebAdmin.h
+++ b/src/WebAdmin.h
@@ -5,7 +5,6 @@
#pragma once
-#include "OSSupport/Socket.h"
#include "Bindings/LuaState.h"
#include "IniFile.h"
#include "HTTPServer/HTTPServer.h"
@@ -135,8 +134,16 @@ public:
/** Returns the prefix needed for making a link point to the webadmin root from the given URL ("../../../webadmin"-style) */
AString GetBaseURL(const AString & a_URL);
- AString GetIPv4Ports(void) const { return m_PortsIPv4; }
- AString GetIPv6Ports(void) const { return m_PortsIPv6; }
+ /** Returns the list of ports used for the webadmin. */
+ AString GetPorts(void) const { return StringsConcat(m_Ports, ','); }
+
+ /** OBSOLETE: Returns the list of IPv4 ports used for the webadmin.
+ Currently there is no distinction between IPv4 and IPv6; use GetPorts() instead. */
+ AString GetIPv4Ports(void) const { return GetPorts(); }
+
+ /** OBSOLETE: Returns the list of IPv6 ports used for the webadmin.
+ Currently there is no distinction between IPv4 and IPv6; use GetPorts() instead. */
+ AString GetIPv6Ports(void) const { return GetPorts(); }
// tolua_end
@@ -205,8 +212,8 @@ protected:
PluginList m_Plugins;
- AString m_PortsIPv4;
- AString m_PortsIPv6;
+ /** The ports on which the webadmin is running. */
+ AStringVector m_Ports;
/** The Lua template script to provide templates: */
cLuaState m_TemplateScript;
diff --git a/src/World.cpp b/src/World.cpp
index 24b1a9b40..474f77b81 100644
--- a/src/World.cpp
+++ b/src/World.cpp
@@ -815,10 +815,9 @@ void cWorld::Stop(void)
// Delete the clients that have been in this world:
{
cCSLock Lock(m_CSClients);
- for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
+ for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
{
(*itr)->Destroy();
- delete *itr;
} // for itr - m_Clients[]
m_Clients.clear();
}
@@ -1093,19 +1092,26 @@ void cWorld::TickScheduledTasks(void)
void cWorld::TickClients(float a_Dt)
{
- cClientHandleList RemoveClients;
+ cClientHandlePtrs RemoveClients;
{
cCSLock Lock(m_CSClients);
// Remove clients scheduled for removal:
- for (cClientHandleList::iterator itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
+ for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
{
- m_Clients.remove(*itr);
+ for (auto itrC = m_Clients.begin(), endC = m_Clients.end(); itrC != endC; ++itrC)
+ {
+ if (itrC->get() == *itr)
+ {
+ m_Clients.erase(itrC);
+ break;
+ }
+ }
} // for itr - m_ClientsToRemove[]
m_ClientsToRemove.clear();
// Add clients scheduled for adding:
- for (cClientHandleList::iterator itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr)
+ for (auto itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr)
{
ASSERT(std::find(m_Clients.begin(), m_Clients.end(), *itr) == m_Clients.end());
m_Clients.push_back(*itr);
@@ -1113,7 +1119,7 @@ void cWorld::TickClients(float a_Dt)
m_ClientsToAdd.clear();
// Tick the clients, take out those that have been destroyed into RemoveClients
- for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();)
+ for (auto itr = m_Clients.begin(); itr != m_Clients.end();)
{
if ((*itr)->IsDestroyed())
{
@@ -1126,12 +1132,9 @@ void cWorld::TickClients(float a_Dt)
++itr;
} // for itr - m_Clients[]
}
-
- // Delete the clients that have been destroyed
- for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr)
- {
- delete *itr;
- } // for itr - RemoveClients[]
+
+ // Delete the clients queued for removal:
+ RemoveClients.clear();
}
@@ -3525,7 +3528,7 @@ void cWorld::AddQueuedPlayers(void)
cCSLock Lock(m_CSClients);
for (cPlayerList::iterator itr = PlayersToAdd.begin(), end = PlayersToAdd.end(); itr != end; ++itr)
{
- cClientHandle * Client = (*itr)->GetClientHandle();
+ cClientHandlePtr Client = (*itr)->GetClientHandlePtr();
if (Client != nullptr)
{
m_Clients.push_back(Client);
diff --git a/src/World.h b/src/World.h
index e7519dab8..3cac71a36 100644
--- a/src/World.h
+++ b/src/World.h
@@ -38,6 +38,9 @@ class cRedstoneSimulator;
class cItem;
class cPlayer;
class cClientHandle;
+typedef SharedPtr<cClientHandle> cClientHandlePtr;
+typedef std::list<cClientHandlePtr> cClientHandlePtrs;
+typedef std::list<cClientHandle *> cClientHandles;
class cEntity;
class cBlockEntity;
class cWorldGenerator; // The generator that actually generates the chunks for a single world
@@ -1019,13 +1022,13 @@ private:
cCriticalSection m_CSClients;
/** List of clients in this world, these will be ticked by this world */
- cClientHandleList m_Clients;
+ cClientHandlePtrs m_Clients;
/** Clients that are scheduled for removal (ticked in another world), waiting for TickClients() to remove them */
- cClientHandleList m_ClientsToRemove;
+ cClientHandles m_ClientsToRemove;
/** Clients that are scheduled for adding, waiting for TickClients to add them */
- cClientHandleList m_ClientsToAdd;
+ cClientHandlePtrs m_ClientsToAdd;
/** Guards m_EntitiesToAdd */
cCriticalSection m_CSEntitiesToAdd;
diff --git a/src/main.cpp b/src/main.cpp
index d4adc1ed9..20609a2f8 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -11,13 +11,17 @@
#include <dbghelp.h>
#endif // _MSC_VER
+#include "OSSupport/NetworkSingleton.h"
-bool cRoot::m_TerminateEventRaised = false; // If something has told the server to stop; checked periodically in cRoot
-static bool g_ServerTerminated = false; // Set to true when the server terminates, so our CTRL handler can then tell the OS to close the console
+/** If something has told the server to stop; checked periodically in cRoot */
+bool cRoot::m_TerminateEventRaised = false;
+
+/** Set to true when the server terminates, so our CTRL handler can then tell the OS to close the console. */
+static bool g_ServerTerminated = false;
/** If set to true, the protocols will log each player's incoming (C->S) communication to a per-connection logfile */
bool g_ShouldLogCommIn;
@@ -305,6 +309,9 @@ int main( int argc, char **argv)
g_ServerTerminated = true;
+ // Shutdown all of LibEvent:
+ cNetworkSingleton::Get().Terminate();
+
return EXIT_SUCCESS;
}
diff --git a/tests/Network/EchoServer.cpp b/tests/Network/EchoServer.cpp
index 728db0b7c..5f4b7651d 100644
--- a/tests/Network/EchoServer.cpp
+++ b/tests/Network/EchoServer.cpp
@@ -7,6 +7,7 @@
#include <iostream>
#include <string>
#include "OSSupport/Network.h"
+#include "OSSupport/NetworkSingleton.h"
@@ -98,7 +99,7 @@ class cEchoServerCallbacks:
-int main()
+void DoTest(void)
{
LOGD("EchoServer: starting up");
cServerHandlePtr Server = cNetwork::Listen(9876, std::make_shared<cEchoServerCallbacks>());
@@ -119,9 +120,20 @@ int main()
Server->Close();
ASSERT(!Server->IsListening());
LOGD("Server has been closed.");
+}
+
+
+
+
+
+int main()
+{
+ DoTest();
printf("Press enter to exit test.\n");
+ AString line;
std::getline(std::cin, line);
+ cNetworkSingleton::Get().Terminate();
LOG("Network test finished.");
return 0;
diff --git a/tests/Network/Google.cpp b/tests/Network/Google.cpp
index 2b8830c24..54bfa6de4 100644
--- a/tests/Network/Google.cpp
+++ b/tests/Network/Google.cpp
@@ -7,6 +7,7 @@
#include <thread>
#include "OSSupport/Event.h"
#include "OSSupport/Network.h"
+#include "OSSupport/NetworkSingleton.h"
@@ -96,7 +97,7 @@ public:
-int main()
+void DoTest(void)
{
cEvent evtFinish;
@@ -109,7 +110,19 @@ int main()
LOGD("Connect request has been queued.");
evtFinish.Wait();
+}
+
+
+
+
+
+int main()
+{
+ DoTest();
+
+ cNetworkSingleton::Get().Terminate();
LOGD("Network test finished");
+
return 0;
}
diff --git a/tests/Network/NameLookup.cpp b/tests/Network/NameLookup.cpp
index 822a96baf..16fa8042b 100644
--- a/tests/Network/NameLookup.cpp
+++ b/tests/Network/NameLookup.cpp
@@ -7,6 +7,7 @@
#include <thread>
#include "OSSupport/Event.h"
#include "OSSupport/Network.h"
+#include "OSSupport/NetworkSingleton.h"
@@ -45,7 +46,7 @@ public:
-int main()
+void DoTest(void)
{
cEvent evtFinish;
@@ -70,7 +71,16 @@ int main()
LOGD("IP lookup has been successfully queued");
evtFinish.Wait();
LOGD("IP lookup finished.");
+}
+
+
+
+
+int main()
+{
+ DoTest();
+ cNetworkSingleton::Get().Terminate();
LOGD("Network test finished");
return 0;
}